Serviceها در لاراول؛ راهنمای کامل Service Layer

تاریخ انتشار: 2026/05/27 04:38 بازدید: 13 نویسنده: Admin

Serviceها در لاراول یکی از مهم‌ترین الگوهای معماری برای تمیزتر کردن کد، کاهش وابستگی Controllerها، جداسازی منطق تجاری و افزایش تست‌پذیری پروژه هستند. برخلاف Model، Controller یا Middleware، لاراول به‌صورت پیش‌فرض پوشه یا دستور اختصاصی برای Serviceها ارائه نمی‌کند؛ اما در پروژه‌های حرفه‌ای، ساخت Service Layer یکی از بهترین روش‌ها برای مدیریت منطق پیچیده، تعامل با سیستم‌های خارجی، پردازش سفارش، پرداخت، ارسال پیامک، مدیریت فایل، گزارش‌گیری و عملیات چندمرحله‌ای است. در این مقاله، به‌صورت کامل و فنی با مفهوم Service در Laravel، ساختار پیشنهادی، نحوه استفاده در Controller، ارتباط با Service Container، Dependency Injection، Service Provider، تست‌نویسی و بهترین روش‌های پیاده‌سازی Serviceها آشنا می‌شویم.

1.0x

برای شنیدن متن، روی «پخش صوت مقاله» بزنید.

مقدمه

در پروژه‌های کوچک لاراولی، معمولاً توسعه‌دهنده منطق برنامه را مستقیم داخل Controller یا Model می‌نویسد. برای مثال، یک متد store در کنترلر ممکن است داده ورودی را اعتبارسنجی کند، محصول را بسازد، موجودی انبار را تغییر دهد، فاکتور ایجاد کند، پیامک ارسال کند، ایمیل بفرستد، Cache را پاک کند و در نهایت کاربر را Redirect کند. این روش در ابتدا سریع به نظر می‌رسد، اما با رشد پروژه به یکی از اصلی‌ترین دلایل پیچیدگی، تکرار کد، سختی تست‌نویسی و کاهش کیفیت نرم‌افزار تبدیل می‌شود.

اینجاست که مفهوم Service Layer یا Service Class اهمیت پیدا می‌کند. Serviceها در لاراول کلاس‌هایی هستند که منطق تجاری برنامه را از Controller، Model، Job و سایر بخش‌ها جدا می‌کنند. هدف Service این است که یک فرآیند مشخص از کسب‌وکار یا برنامه را در یک کلاس مستقل، قابل استفاده مجدد و قابل تست پیاده‌سازی کند.

برای مثال، در یک نرم‌افزار فروشگاهی، عملیات «ثبت سفارش» فقط یک Order::create() ساده نیست. این عملیات ممکن است شامل بررسی موجودی، محاسبه تخفیف، ثبت آیتم‌های سفارش، محاسبه مالیات، اتصال به درگاه پرداخت، ایجاد تراکنش، ارسال پیامک، ثبت لاگ و ارسال اعلان باشد. اگر همه این منطق داخل Controller نوشته شود، کنترلر خیلی زود به یک کلاس سنگین و غیرقابل نگهداری تبدیل می‌شود. اما اگر این منطق داخل OrderService قرار بگیرد، کنترلر سبک‌تر و معماری برنامه تمیزتر خواهد شد.

نکته مهم این است که Laravel به‌صورت رسمی شما را مجبور نمی‌کند پوشه‌ای به نام Services داشته باشید. اما امکاناتی مثل Service Container، Dependency Injection و Service Provider در لاراول به‌خوبی از این نوع معماری پشتیبانی می‌کنند. طبق مستندات رسمی Laravel، Service Container ابزار قدرتمند لاراول برای مدیریت وابستگی‌های کلاس‌ها و انجام Dependency Injection است و بسیاری از کلاس‌های برنامه مانند Controllerها، Event Listenerها، Middlewareها و Jobها می‌توانند وابستگی‌های خود را به‌صورت خودکار از Container دریافت کنند.

در این مقاله، به‌صورت کامل و فنی بررسی می‌کنیم که Serviceها در لاراول چه هستند، چه زمانی باید از آن‌ها استفاده کنیم، چه تفاوتی با Controller و Model دارند، چطور ساخته می‌شوند، چگونه با Dependency Injection و Service Container کار می‌کنند و در پروژه‌های شرکتی چه Best Practiceهایی برای طراحی آن‌ها وجود دارد. 🚀

Service در لاراول چیست؟

Service در Laravel معمولاً یک کلاس PHP مستقل است که بخشی از منطق تجاری یا فرآیندهای کاربردی برنامه را مدیریت می‌کند. این کلاس‌ها معمولاً در مسیر زیر قرار داده می‌شوند:

app/Services

 

البته این مسیر اجباری نیست. شما می‌توانید بر اساس ساختار پروژه از مسیرهایی مثل موارد زیر استفاده کنید:

app/Services
app/Domain/Order/Services
app/Actions
app/Application/Services

 

اما در بسیاری از پروژه‌های شرکتی لاراول، پوشه app/Services یک انتخاب ساده، قابل فهم و رایج است.

نمونه یک Service ساده:

 

<?php

namespace App\Services;

use App\Models\Product;

class ProductService
{
    public function create(array $data): Product
    {
        return Product::create($data);
    }

    public function update(Product $product, array $data): Product
    {
        $product->update($data);

        return $product;
    }
}

 

و استفاده در Controller:

 

<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreProductRequest;
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('products.show', $product);
    }
}

 

در اینجا Controller فقط درخواست را دریافت می‌کند، داده اعتبارسنجی‌شده را به Service می‌دهد و پاسخ مناسب برمی‌گرداند. منطق ایجاد محصول داخل ProductService قرار گرفته است.

چرا Serviceها در لاراول مهم هستند؟

Serviceها به‌خصوص در پروژه‌های متوسط و بزرگ اهمیت زیادی دارند. دلیل اصلی این است که با رشد پروژه، منطق برنامه به‌مرور پیچیده‌تر می‌شود. اگر این منطق در Controllerها، Modelها یا Routeها پراکنده شود، نگهداری پروژه سخت خواهد شد.

مزایای استفاده از Serviceها

مزیتتوضیح
کاهش حجم Controllerکنترلر فقط نقش هماهنگ‌کننده دارد و منطق اصلی به Service منتقل می‌شود
افزایش تست‌پذیریServiceها را می‌توان مستقل از HTTP Request تست کرد
کاهش تکرار کدمنطق مشترک بین Controller، Job، Command و API در یک کلاس قرار می‌گیرد
خوانایی بهترهر Service مسئول یک بخش مشخص از منطق برنامه است
نگهداری ساده‌ترتغییرات تجاری در یک نقطه متمرکز می‌شود
جداسازی مسئولیت‌هاController، Model و Service هرکدام نقش مشخص‌تری دارند
مناسب توسعه تیمیاعضای تیم راحت‌تر می‌توانند ساختار پروژه را درک کنند
پشتیبانی بهتر از Dependency Injectionوابستگی‌ها به‌صورت تمیز از طریق Constructor دریافت می‌شوند

فرض کنید در یک پروژه هم از پنل مدیریت، هم API موبایل و هم یک Job زمان‌بندی‌شده نیاز دارید محصولی را فعال کنید. اگر منطق فعال‌سازی محصول داخل Controller باشد، در بخش‌های دیگر باید همان منطق را تکرار کنید. اما اگر این منطق داخل ProductService باشد، همه بخش‌ها می‌توانند از همان Service استفاده کنند.

Service Layer چیست؟

Service Layer یک لایه معماری بین Controller و بخش‌های پایین‌تر برنامه مثل Model، Repository، سیستم‌های خارجی یا APIهای دیگر است. این لایه معمولاً منطق تجاری برنامه را نگهداری می‌کند.

در یک معماری ساده لاراولی، جریان درخواست می‌تواند این‌گونه باشد:

Route → Controller → Service → Model/Repository/External API → Response

 

برای مثال:

ثبت سفارش:
Route → OrderController → OrderService → Order / PaymentGateway / SmsService

 

در این ساختار، Controller مستقیماً همه کارها را انجام نمی‌دهد. بلکه درخواست را دریافت کرده و اجرای فرآیند را به Service می‌سپارد.

تفاوت Service با Controller

Controller وظیفه مدیریت HTTP Request و Response را دارد. یعنی Controller باید با Request، Redirect، View، JSON Response و Route Model Binding سروکار داشته باشد. اما Service نباید وابسته به لایه HTTP باشد، مگر در موارد خاص.

موردControllerService
مسئولیت اصلیدریافت Request و بازگرداندن Responseاجرای منطق تجاری
وابستگی به HTTPزیادکم یا صفر
محل استفادهRouteهای Web و APIController، Job، Command، Listener، تست
مناسب برایهماهنگ‌سازی درخواستپیاده‌سازی فرآیندهای برنامه
تست‌پذیریمعمولاً با Feature Testمعمولاً با Unit Test یا Integration Test
خروجیView، Redirect، JSONObject، Array، DTO، Result

نمونه Controller نامناسب:

 

public function store(Request $request)
{
    $data = $request->validate([
        'customer_id' => ['required', 'exists:customers,id'],
        'items' => ['required', 'array'],
    ]);

    $order = Order::create([
        'customer_id' => $data['customer_id'],
        'status' => 'pending',
    ]);

    foreach ($data['items'] as $item) {
        $product = Product::findOrFail($item['product_id']);

        if ($product->stock < $item['quantity']) {
            throw new \Exception('Insufficient stock');
        }

        $order->items()->create([
            'product_id' => $product->id,
            'quantity' => $item['quantity'],
            'price' => $product->price,
        ]);

        $product->decrement('stock', $item['quantity']);
    }

    Mail::to($order->customer->email)->send(new OrderCreatedMail($order));

    return redirect()->route('orders.show', $order);
}

 

این کنترلر بیش از حد کار انجام می‌دهد. نسخه بهتر:

 

public function store(StoreOrderRequest $request)
{
    $order = $this->orderService->createOrder(
        $request->validated()
    );

    return redirect()->route('orders.show', $order);
}

 

در این نسخه، کنترلر ساده و قابل فهم است. منطق اصلی داخل OrderService قرار دارد.

تفاوت Service با Model

Model در لاراول نماینده داده و ارتباط با جدول دیتابیس است. مدل‌ها رابطه‌ها، Scopeها، Castها، Accessorها، Mutatorها و رفتارهای نزدیک به داده را مدیریت می‌کنند. اما Service معمولاً فرآیندهای سطح بالاتر کسب‌وکار را انجام می‌دهد.

موردModelService
مسئولیتنمایش Entity و ارتباط با دیتابیساجرای فرآیند تجاری
مثالProduct, Order, UserOrderService, PaymentService
شامل رابطه‌هابلهمعمولاً خیر
شامل منطق چندمرحله‌ایمحدودبله
ارتباط با API خارجیبهتر است مستقیم نباشدمناسب‌تر است
محل قرارگیریapp/Modelsapp/Services

مثلاً متد زیر می‌تواند در Model منطقی باشد:

 

public function isAvailable(): bool
{
    return $this->stock > 0 && $this->status === 'active';
}

 

اما فرآیند زیر بهتر است داخل Service باشد:

 

public function createOrder(array $data): Order
{
    // Validate business rules
    // Create order
    // Create order items
    // Update stock
    // Create payment
    // Send notifications
}

 

Model باید رفتارهای نزدیک به خود Entity را نگه دارد، نه کل فرآیندهای پیچیده کسب‌وکار را.

چه زمانی باید از Service در Laravel استفاده کنیم؟

همیشه لازم نیست برای هر عملیات ساده یک Service بسازید. اگر فقط یک رکورد ساده ذخیره می‌کنید، شاید نوشتن مستقیم آن در Controller قابل قبول باشد. اما در شرایط زیر استفاده از Service بسیار توصیه می‌شود:

1. وقتی منطق تجاری پیچیده است

مثلاً ثبت سفارش، محاسبه پورسانت، تمدید اشتراک، مدیریت پرداخت یا صدور فاکتور.

2. وقتی یک منطق در چند جای پروژه استفاده می‌شود

مثلاً هم Controller و هم Job و هم Command باید از یک فرآیند مشترک استفاده کنند.

3. وقتی با سیستم خارجی ارتباط دارید

مثل درگاه پرداخت، سرویس پیامک، API حسابداری، CRM خارجی یا سرویس ارسال ایمیل.

4. وقتی نیاز به تست مستقل دارید

Serviceها را می‌توان بدون وابستگی مستقیم به HTTP تست کرد.

5. وقتی Controller بیش از حد بزرگ شده است

اگر کنترلر شما طولانی، شرطی و پیچیده شده، احتمالاً زمان ساخت Service فرا رسیده است.

6. وقتی عملیات چند مدل را درگیر می‌کند

مثلاً ثبت سفارش هم با Order، هم OrderItem، هم Product، هم Payment و هم Customer سروکار دارد.

ساخت Service در لاراول

لاراول دستور Artisan پیش‌فرضی مثل make:service ندارد. بنابراین معمولاً فایل Service را دستی می‌سازیم.

مسیر پیشنهادی:

app/Services/ProductService.php

 

نمونه:

 

<?php

namespace App\Services;

use App\Models\Product;

class ProductService
{
    public function create(array $data): Product
    {
        return Product::create($data);
    }
}

 

برای ساختارهای بزرگ‌تر می‌توان Serviceها را بر اساس دامنه تقسیم کرد:

app/Services/
  ProductService.php
  OrderService.php
  PaymentService.php
  SmsService.php
  ReportService.php

 

یا ساختار دامنه‌محور:

app/Domain/
  Order/
    Services/
      CreateOrderService.php
      CancelOrderService.php
  Payment/
    Services/
      PaymentGatewayService.php

 

در پروژه‌های شرکتی، انتخاب ساختار باید بر اساس اندازه پروژه، تعداد تیم، پیچیدگی دامنه و آینده محصول انجام شود.

مثال ساده Service در لاراول

فرض کنید می‌خواهیم منطق ایجاد محصول را از Controller جدا کنیم.

ProductService

 

<?php

namespace App\Services;

use App\Models\Product;
use Illuminate\Support\Str;

class ProductService
{
    public function create(array $data): Product
    {
        $data['slug'] = Str::slug($data['title']);

        return Product::create($data);
    }

    public function update(Product $product, array $data): Product
    {
        if (isset($data['title'])) {
            $data['slug'] = Str::slug($data['title']);
        }

        $product->update($data);

        return $product;
    }
}

 

ProductController

 

<?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', 'محصول با موفقیت ویرایش شد.');
    }
}

 

در این طراحی، Controller فقط عملیات HTTP را مدیریت می‌کند و منطق ساخت slug داخل Service قرار گرفته است.

مثال پیشرفته: OrderService برای ثبت سفارش

ثبت سفارش نمونه‌ای عالی برای استفاده از Service است؛ چون معمولاً چندین مرحله دارد.

 

<?php

namespace App\Services;

use App\Models\Order;
use App\Models\Product;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class OrderService
{
    public function createOrder(array $data): Order
    {
        return DB::transaction(function () use ($data) {
            $order = Order::create([
                'customer_id' => $data['customer_id'],
                'status' => 'pending',
                'total_price' => 0,
            ]);

            $totalPrice = 0;

            foreach ($data['items'] as $item) {
                $product = Product::lockForUpdate()
                    ->findOrFail($item['product_id']);

                if ($product->stock < $item['quantity']) {
                    throw new RuntimeException('موجودی محصول کافی نیست.');
                }

                $lineTotal = $product->price * $item['quantity'];

                $order->items()->create([
                    'product_id' => $product->id,
                    'quantity' => $item['quantity'],
                    'unit_price' => $product->price,
                    'total_price' => $lineTotal,
                ]);

                $product->decrement('stock', $item['quantity']);

                $totalPrice += $lineTotal;
            }

            $order->update([
                'total_price' => $totalPrice,
            ]);

            return $order;
        });
    }
}

 

در این مثال، OrderService چند کار مهم انجام می‌دهد:

  • ایجاد سفارش
  • بررسی موجودی محصول
  • قفل کردن رکورد برای جلوگیری از Race Condition
  • ایجاد آیتم‌های سفارش
  • کاهش موجودی
  • محاسبه مبلغ کل
  • اجرای همه مراحل داخل Transaction

چنین منطقی اگر داخل Controller قرار بگیرد، کنترلر بسیار شلوغ خواهد شد. اما در Service، ساختار برنامه تمیزتر و تست‌پذیرتر می‌شود.

Service و Dependency Injection در Laravel

یکی از دلایل مهمی که Serviceها در لاراول خوب کار می‌کنند، پشتیبانی Laravel از Dependency Injection است. طبق مستندات رسمی Laravel، Service Container برای مدیریت وابستگی کلاس‌ها و تزریق آن‌ها استفاده می‌شود و بسیاری از کلاس‌ها مانند Controllerها به‌صورت خودکار وابستگی‌های خود را از Container دریافت می‌کنند.

یعنی شما می‌توانید Service را در Constructor کنترلر Type-Hint کنید:

 

public function __construct(
    protected OrderService $orderService
) {}

 

لاراول به‌صورت خودکار OrderService را می‌سازد و به کنترلر تزریق می‌کند، البته تا زمانی که وابستگی‌های آن قابل Resolve شدن باشند.

نمونه Service با وابستگی:

 

<?php

namespace App\Services;

use App\Contracts\PaymentGatewayInterface;

class PaymentService
{
    public function __construct(
        protected PaymentGatewayInterface $gateway
    ) {}

    public function pay(int $amount): array
    {
        return $this->gateway->charge($amount);
    }
}

 

در این حالت، اگر از Interface استفاده کنیم، باید به Laravel بگوییم کدام کلاس باید برای این Interface ساخته شود. این کار با Service Container و Service Provider انجام می‌شود.

Service Container و نقش آن در Serviceها

Service Container قلب مدیریت وابستگی‌ها در Laravel است. وقتی یک کلاس در Constructor خود کلاس دیگری را Type-Hint می‌کند، Laravel تلاش می‌کند وابستگی را Resolve کند. اگر وابستگی یک کلاس ساده باشد، معمولاً Laravel خودش آن را می‌سازد. اما اگر وابستگی یک Interface باشد، باید Binding تعریف شود.

نمونه Interface:

 

<?php

namespace App\Contracts;

interface SmsGatewayInterface
{
    public function send(string $mobile, string $message): bool;
}

 

پیاده‌سازی:

 

<?php

namespace App\Services\Sms;

use App\Contracts\SmsGatewayInterface;

class KavenegarSmsGateway implements SmsGatewayInterface
{
    public function send(string $mobile, string $message): bool
    {
        // Send SMS via provider API

        return true;
    }
}

 

Service:

 

<?php

namespace App\Services;

use App\Contracts\SmsGatewayInterface;

class NotificationService
{
    public function __construct(
        protected SmsGatewayInterface $sms
    ) {}

    public function sendOrderCreatedMessage(string $mobile): bool
    {
        return $this->sms->send(
            $mobile,
            'سفارش شما با موفقیت ثبت شد.'
        );
    }
}

 

حالا باید Binding تعریف شود:

 

use App\Contracts\SmsGatewayInterface;
use App\Services\Sms\KavenegarSmsGateway;

$this->app->bind(SmsGatewayInterface::class, KavenegarSmsGateway::class);

 

این Binding معمولاً داخل یک Service Provider انجام می‌شود.

Service Provider و ارتباط آن با Serviceها

Service Provider در Laravel محل ثبت Bindingها، تنظیمات و Bootstrap شدن بخش‌های مختلف برنامه است. طبق مستندات رسمی Laravel، همه Service Providerها از کلاس Illuminate\Support\ServiceProvider ارث‌بری می‌کنند و بیشتر آن‌ها دو متد register و boot دارند؛ در متد register باید فقط Bindingها در Service Container ثبت شوند.

ساخت Service Provider:

 

php artisan make:provider AppServiceProvider

 

یا ساخت Provider اختصاصی:

 

php artisan make:provider PaymentServiceProvider

 

نمونه:

 

<?php

namespace App\Providers;

use App\Contracts\PaymentGatewayInterface;
use App\Services\Payment\ZarinpalGateway;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            PaymentGatewayInterface::class,
            ZarinpalGateway::class
        );
    }

    public function boot(): void
    {
        //
    }
}

 

در Laravel 12، Service Providerهای تعریف‌شده توسط کاربر در فایل bootstrap/providers.php ثبت می‌شوند. مستندات رسمی Laravel نیز بخش Service Providers را به نحوه نوشتن و ثبت Providerهای سفارشی اختصاص داده است.

bind و singleton در Service Container

هنگام ثبت Service در Container، معمولاً با دو مفهوم مهم مواجه می‌شویم: bind و singleton.

bind

هر بار که کلاس Resolve شود، یک نمونه جدید ساخته می‌شود:

 

$this->app->bind(
    PaymentGatewayInterface::class,
    ZarinpalGateway::class
);

 

singleton

فقط یک نمونه ساخته می‌شود و در درخواست‌های بعدی همان نمونه برگردانده می‌شود:

 

$this->app->singleton(
    CurrencyRateService::class,
    fn () => new CurrencyRateService()
);

 

استفاده از singleton برای سرویس‌هایی مناسب است که نگهداری یک نمونه مشترک از آن‌ها منطقی است. اما برای سرویس‌هایی که State متغیر و حساس دارند، باید با احتیاط از Singleton استفاده کرد.

ساختار پیشنهادی Serviceها در پروژه شرکتی

در یک پروژه شرکتی متوسط، می‌توان از ساختار زیر استفاده کرد:

app/
  Http/
    Controllers/
    Requests/
    Resources/
  Models/
  Services/
    ProductService.php
    OrderService.php
    PaymentService.php
    SmsService.php
    ReportService.php
  Contracts/
    PaymentGatewayInterface.php
    SmsGatewayInterface.php
  Providers/
    PaymentServiceProvider.php

 

برای پروژه‌های بزرگ‌تر، ساختار دامنه‌محور بهتر است:

app/
  Domain/
    Order/
      Services/
        CreateOrderService.php
        CancelOrderService.php
      Data/
        OrderData.php
      Events/
        OrderCreated.php
    Payment/
      Services/
        PaymentService.php
      Contracts/
        PaymentGatewayInterface.php
      Gateways/
        ZarinpalGateway.php

 

ساختار دوم برای تیم‌های بزرگ و پروژه‌های Enterprise مناسب‌تر است، اما برای پروژه‌های کوچک ممکن است بیش از حد پیچیده باشد.

Service، Action و Repository چه تفاوتی دارند؟

در معماری Laravel گاهی مفاهیم Service، Action و Repository با هم اشتباه گرفته می‌شوند.

مفهومهدف اصلیمثال
Serviceمدیریت منطق تجاری یا فرآیندهای برنامهOrderService, PaymentService
Actionانجام یک کار مشخص و محدودCreateOrderAction, SendInvoiceAction
Repositoryجداسازی منطق دسترسی به دادهProductRepository
Modelنمایش Entity و تعامل با دیتابیسProduct, Order

Service می‌تواند شامل چند متد مرتبط باشد:

 

$orderService->createOrder();
$orderService->cancelOrder();
$orderService->refundOrder();

 

اما Action معمولاً فقط یک کار انجام می‌دهد:

 

$createOrderAction->execute($data);

 

Repository بیشتر برای Queryها و دسترسی به داده استفاده می‌شود، نه اجرای منطق تجاری.

در بسیاری از پروژه‌های Laravel، استفاده از Service کافی است و نیازی نیست Repository یا Action را بی‌دلیل اضافه کنیم. معماری خوب یعنی حل مشکل واقعی، نه زیاد کردن تعداد کلاس‌ها.

Service برای ارتباط با APIهای خارجی

یکی از بهترین کاربردهای Serviceها، مدیریت ارتباط با سرویس‌های خارجی است. مثلاً درگاه پرداخت، پیامک، سرویس ایمیل، API حسابداری، سرویس حمل‌ونقل یا CRM خارجی.

نمونه PaymentGatewayService:

 

<?php

namespace App\Services\Payment;

use Illuminate\Support\Facades\Http;

class ZarinpalGateway
{
    public function requestPayment(int $amount, string $callbackUrl): array
    {
        $response = Http::post('https://example-payment-provider.test/request', [
            'amount' => $amount,
            'callback_url' => $callbackUrl,
        ]);

        return $response->json();
    }

    public function verifyPayment(string $authority, int $amount): array
    {
        $response = Http::post('https://example-payment-provider.test/verify', [
            'authority' => $authority,
            'amount' => $amount,
        ]);

        return $response->json();
    }
}

 

Controller نباید مستقیماً با API خارجی کار کند. اگر روزی درگاه پرداخت تغییر کند، فقط Service مربوطه تغییر می‌کند و Controllerها دست‌نخورده باقی می‌مانند.

Service و Queue در لاراول

گاهی یک Service عملیاتی انجام می‌دهد که زمان‌بر است؛ مثل ارسال ایمیل انبوه، پردازش فایل، تولید گزارش، اتصال به API خارجی یا ارسال چندین پیامک. در این موارد بهتر است عملیات زمان‌بر را به Job و Queue منتقل کنیم.

Laravel Queue برای اجرای کارهای زمان‌بر در پس‌زمینه استفاده می‌شود و طبق مستندات رسمی Laravel، Queueها از Connectionهای مختلفی مانند Redis، Amazon SQS، Beanstalk و سایر Backendها پشتیبانی می‌کنند.

ساخت Job:

 

php artisan make:job SendOrderCreatedNotification

 

نمونه استفاده از Service در Job:

 

<?php

namespace App\Jobs;

use App\Models\Order;
use App\Services\NotificationService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class SendOrderCreatedNotification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public Order $order
    ) {}

    public function handle(NotificationService $notificationService): void
    {
        $notificationService->sendOrderCreated($this->order);
    }
}

 

در اینجا Job مسئول اجرای پس‌زمینه است و Service مسئول منطق ارسال اعلان. این تفکیک باعث می‌شود کد تمیزتر و قابل تست‌تر باشد.

Service و Transaction دیتابیس

اگر Service شامل چند عملیات دیتابیس است که باید همه با هم موفق یا ناموفق شوند، باید از Transaction استفاده کرد.

نمونه:

 

use Illuminate\Support\Facades\DB;

class SubscriptionService
{
    public function renew(array $data): Subscription
    {
        return DB::transaction(function () use ($data) {
            $subscription = Subscription::findOrFail($data['subscription_id']);

            $subscription->update([
                'expires_at' => now()->addMonth(),
            ]);

            Payment::create([
                'subscription_id' => $subscription->id,
                'amount' => $data['amount'],
                'status' => 'paid',
            ]);

            return $subscription;
        });
    }
}

 

اگر یکی از عملیات‌ها خطا بدهد، Transaction باعث می‌شود تغییرات قبلی Rollback شوند. این موضوع در فرآیندهای مالی، سفارش، انبار و اشتراک اهمیت بسیار زیادی دارد.

Service و تست‌نویسی در لاراول

یکی از بزرگ‌ترین مزایای Serviceها، تست‌پذیری بهتر است. وقتی منطق تجاری داخل Controller باشد، برای تست آن معمولاً باید Request HTTP شبیه‌سازی شود. اما وقتی منطق داخل Service باشد، می‌توان آن را مستقل‌تر تست کرد.

نمونه تست ساده:

 

<?php

namespace Tests\Feature;

use App\Models\Product;
use App\Services\ProductService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ProductServiceTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_can_create_product(): void
    {
        $service = app(ProductService::class);

        $product = $service->create([
            'title' => 'CRM Software',
            'price' => 5000000,
        ]);

        $this->assertInstanceOf(Product::class, $product);
        $this->assertDatabaseHas('products', [
            'title' => 'CRM Software',
        ]);
    }
}

 

اگر Service وابسته به API خارجی باشد، می‌توان آن وابستگی را Mock کرد. این کار باعث می‌شود تست‌ها سریع‌تر، پایدارتر و مستقل‌تر باشند.

خروجی Service باید چه باشد؟

یک Service می‌تواند بسته به نوع عملیات خروجی‌های مختلفی داشته باشد:

نوع خروجیکاربرد
Modelوقتی عملیات یک Entity ایجاد یا ویرایش می‌کند
Collectionوقتی لیستی از داده‌ها برمی‌گردد
Arrayبرای داده‌های ساده یا پاسخ API خارجی
DTOبرای ساختارهای حرفه‌ای‌تر و Type-Safe
Booleanبرای عملیات موفق/ناموفق ساده
Result Objectبرای فرآیندهای پیچیده با پیام، وضعیت و داده

مثلاً:

 

public function create(array $data): Product
{
    return Product::create($data);
}

 

یا:

 

public function send(string $mobile, string $message): bool
{
    return $this->gateway->send($mobile, $message);
}

 

در پروژه‌های بزرگ، استفاده از DTO یا Result Object می‌تواند خوانایی و کنترل خطا را بهتر کند.

مدیریت خطا در Serviceها

Serviceها باید خطاها را شفاف و قابل کنترل مدیریت کنند. مثلاً اگر موجودی محصول کافی نیست، بهتر است یک Exception مشخص ایجاد شود.

 

namespace App\Exceptions;

use Exception;

class InsufficientStockException extends Exception
{
    //
}

 

استفاده در Service:

 

if ($product->stock < $quantity) {
    throw new InsufficientStockException('موجودی محصول کافی نیست.');
}

 

سپس Controller یا Handler می‌تواند این خطا را به پاسخ مناسب تبدیل کند.

 

try {
    $order = $this->orderService->createOrder($data);
} catch (InsufficientStockException $exception) {
    return back()->withErrors([
        'stock' => $exception->getMessage(),
    ]);
}

 

در APIها نیز می‌توان خطا را به JSON Response مناسب تبدیل کرد.

Serviceها و قوانین SOLID

Serviceها اگر درست طراحی شوند، به رعایت اصول SOLID کمک می‌کنند.

Single Responsibility Principle

هر Service باید مسئول یک بخش مشخص باشد. مثلاً PaymentService نباید همزمان مسئول پرداخت، ارسال پیامک، تولید گزارش و مدیریت کاربر باشد.

Open/Closed Principle

با استفاده از Interfaceها می‌توان رفتارها را قابل توسعه کرد، بدون اینکه کدهای قبلی زیاد تغییر کنند.

Dependency Inversion Principle

Service بهتر است به Interface وابسته باشد، نه پیاده‌سازی مستقیم.

مثال:

 

public function __construct(
    protected PaymentGatewayInterface $gateway
) {}

 

این کار باعث می‌شود تغییر درگاه پرداخت ساده‌تر شود.

اشتباهات رایج در طراحی Serviceها

1. ساخت Service برای هر چیز کوچک

اگر برای هر متد ساده یک Service بسازید، پروژه بی‌دلیل پیچیده می‌شود. Service باید مشکل واقعی حل کند.

2. تبدیل Service به God Class

اگر OrderService شامل ده‌ها متد نامرتبط و هزاران خط کد باشد، خودش به مشکل تبدیل می‌شود. در این حالت باید آن را به Serviceها یا Actionهای کوچک‌تر تقسیم کرد.

3. وابسته کردن Service به Request

Service بهتر است آرایه، DTO یا پارامترهای مشخص دریافت کند، نه خود Request.

نامناسب:

 

public function create(Request $request)
{
    //
}

 

بهتر:

 

public function create(array $data)
{
    //
}

 

4. بازگرداندن Response از Service

Service نباید redirect() یا response()->json() برگرداند. این کار مربوط به Controller است.

نامناسب:

 

return redirect()->route('orders.index');

 

بهتر:

 

return $order;

 

5. قرار دادن Queryهای گزارش‌گیری پیچیده در Service عمومی

اگر Queryهای گزارش‌گیری خیلی پیچیده هستند، بهتر است از ReportService، Query Object یا کلاس اختصاصی استفاده شود.

6. استفاده نکردن از Interface برای سرویس‌های قابل تعویض

برای سرویس‌هایی مثل پیامک، پرداخت و API خارجی، Interface کمک می‌کند پیاده‌سازی‌ها راحت‌تر تغییر کنند.

بهترین روش‌های طراحی Service در Laravel

1. نام Service را واضح انتخاب کنید

نام‌هایی مثل OrderService، PaymentService و InvoiceService خوب هستند، اما اگر عملیات خیلی مشخص است، نام‌هایی مثل CreateOrderService یا CancelSubscriptionService بهترند.

2. Service را مستقل از HTTP نگه دارید

Service نباید به Request، RedirectResponse یا View وابسته باشد. ورودی تمیز بگیرد و خروجی قابل استفاده برگرداند.

3. از Dependency Injection استفاده کنید

وابستگی‌ها را با new داخل متد نسازید. آن‌ها را از Constructor دریافت کنید.

4. منطق اعتبارسنجی ورودی را در Form Request نگه دارید

Service بهتر است با داده معتبر کار کند. اعتبارسنجی HTTP معمولاً در Form Request انجام می‌شود.

5. برای عملیات مالی و چندمرحله‌ای از Transaction استفاده کنید

ثبت سفارش، پرداخت، اشتراک و عملیات انبار باید با دقت Transactional طراحی شوند.

6. Serviceهای خارجی را پشت Interface پنهان کنید

برای پیامک، پرداخت، ایمیل، حسابداری و سرویس‌های خارجی از Interface استفاده کنید.

7. عملیات زمان‌بر را به Queue بسپارید

Service می‌تواند Job Dispatch کند یا Job می‌تواند از Service استفاده کند. انتخاب دقیق به معماری پروژه بستگی دارد.

8. Service را قابل تست طراحی کنید

ورودی و خروجی مشخص، وابستگی‌های تزریق‌شده و نبود وابستگی مستقیم به HTTP باعث تست‌پذیری بهتر می‌شود.

9. از ساختار ثابت در تیم استفاده کنید

تیم باید توافق کند Serviceها کجا قرار می‌گیرند، چطور نام‌گذاری می‌شوند و چه مسئولیتی دارند.

10. مستندسازی داخلی داشته باشید

برای Serviceهای حساس مثل پرداخت، سفارش، حسابداری و ارسال پیامک، توضیح کوتاه و تست مناسب بسیار ضروری است.

جدول مقایسه Service با اجزای مهم Laravel

بخشنقش اصلیآیا مناسب منطق تجاری است؟مثال کاربرد
Controllerمدیریت Request و Responseمحدوددریافت فرم و نمایش نتیجه
Modelنمایش Entity و ارتباط با دیتابیسفقط منطق نزدیک به دادهبررسی فعال بودن محصول
Serviceاجرای منطق تجاریبلهثبت سفارش، پرداخت، صدور فاکتور
Form Requestاعتبارسنجی ورودیخیرقوانین فرم ایجاد محصول
Jobاجرای کار پس‌زمینهبخشی از فرآیندارسال ایمیل بعد از ثبت سفارش
Event/Listenerواکنش به رخدادبله، اما غیرمستقیمارسال اعلان بعد از پرداخت
Policyکنترل مجوزخیربررسی اجازه ویرایش محصول
Repositoryدسترسی به دادهنه لزوماًQueryهای سفارشی محصول

نمونه کامل Service در یک پروژه شرکتی

فرض کنیم یک شرکت نرم‌افزاری سیستم اشتراک کاربران دارد. کاربر می‌تواند اشتراک خریداری کند و بعد از پرداخت موفق، اشتراک او فعال شود.

SubscriptionService

 

<?php

namespace App\Services;

use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class SubscriptionService
{
    public function subscribe(User $user, Plan $plan): Subscription
    {
        return DB::transaction(function () use ($user, $plan) {
            $subscription = Subscription::create([
                'user_id' => $user->id,
                'plan_id' => $plan->id,
                'starts_at' => now(),
                'ends_at' => now()->addDays($plan->duration_days),
                'status' => 'active',
            ]);

            $user->update([
                'current_plan_id' => $plan->id,
            ]);

            return $subscription;
        });
    }

    public function cancel(Subscription $subscription): Subscription
    {
        $subscription->update([
            'status' => 'cancelled',
            'cancelled_at' => now(),
        ]);

        return $subscription;
    }
}

 

Controller

 

<?php

namespace App\Http\Controllers;

use App\Models\Plan;
use App\Services\SubscriptionService;
use Illuminate\Http\Request;

class SubscriptionController extends Controller
{
    public function __construct(
        protected SubscriptionService $subscriptionService
    ) {}

    public function store(Request $request, Plan $plan)
    {
        $subscription = $this->subscriptionService->subscribe(
            $request->user(),
            $plan
        );

        return redirect()
            ->route('subscriptions.show', $subscription)
            ->with('success', 'اشتراک با موفقیت فعال شد.');
    }
}

 

در این ساختار، کنترلر فقط درخواست را مدیریت می‌کند و Service منطق فعال‌سازی اشتراک را انجام می‌دهد.

Serviceها در پروژه‌های API محور

در پروژه‌هایی که Laravel به‌عنوان Backend برای اپلیکیشن موبایل یا SPA استفاده می‌شود، Serviceها اهمیت بیشتری پیدا می‌کنند. چون ممکن است یک منطق تجاری از چند مسیر مختلف فراخوانی شود:

  • API موبایل
  • پنل ادمین
  • Job زمان‌بندی‌شده
  • Webhook درگاه پرداخت
  • Command داخلی

اگر منطق داخل Controller باشد، مجبور می‌شوید آن را تکرار کنید. اما اگر داخل Service باشد، همه ورودی‌ها می‌توانند از یک منطق مشترک استفاده کنند.

نمونه:

 

class PaymentCallbackController extends Controller
{
    public function __invoke(Request $request, PaymentService $paymentService)
    {
        $payment = $paymentService->verifyCallback(
            $request->all()
        );

        return response()->json([
            'status' => $payment->status,
        ]);
    }
}

 

در این مثال، Controller فقط Callback را دریافت می‌کند و پردازش اصلی پرداخت در PaymentService انجام می‌شود.

Serviceها و سئو در پروژه‌های لاراولی

شاید در نگاه اول Serviceها ارتباط مستقیمی با SEO نداشته باشند، اما در سایت‌های شرکتی، فروشگاهی و محتوایی، Service Layer می‌تواند به کیفیت سئو کمک کند.

برای مثال، در یک سایت محتوایی می‌توان PostPublishingService داشت که هنگام انتشار مقاله این کارها را انجام دهد:

  • بررسی کامل بودن عنوان سئو
  • بررسی وجود Meta Description
  • ساخت Slug استاندارد
  • ایجاد Canonical URL
  • ثبت تاریخ انتشار
  • پاک‌سازی Cache صفحات
  • ارسال Ping به سرویس‌های داخلی
  • تولید Sitemap جدید

نمونه ساده:

 

class PostPublishingService
{
    public function publish(Post $post): Post
    {
        if (! $post->meta_title || ! $post->meta_description) {
            throw new RuntimeException('اطلاعات سئو کامل نیست.');
        }

        $post->update([
            'status' => 'published',
            'published_at' => now(),
        ]);

        cache()->forget('latest_posts');

        return $post;
    }
}

 

در این حالت، منطق انتشار مقاله از Controller جدا شده و اگر انتشار از پنل مدیریت، Job زمان‌بندی‌شده یا API انجام شود، همه از یک Service مشترک استفاده می‌کنند.

FAQ؛ سوالات متداول درباره Serviceها در لاراول

1. Service در لاراول چیست؟

Service یک کلاس مستقل برای مدیریت منطق تجاری یا فرآیندهای کاربردی برنامه است. Serviceها معمولاً برای جدا کردن منطق پیچیده از Controller و Model استفاده می‌شوند.

2. آیا Laravel دستور make:service دارد؟

به‌صورت پیش‌فرض، Laravel دستور رسمی make:service ندارد. معمولاً پوشه app/Services و فایل‌های Service به‌صورت دستی ساخته می‌شوند.

3. Serviceها در کدام مسیر قرار می‌گیرند؟

مسیر رایج app/Services است. اما در پروژه‌های بزرگ می‌توان از ساختارهایی مثل app/Domain/*/Services استفاده کرد.

4. تفاوت Service با Controller چیست؟

Controller درخواست HTTP را دریافت و پاسخ مناسب برمی‌گرداند، اما Service منطق تجاری را اجرا می‌کند. Service نباید مستقیماً مسئول Redirect، View یا JSON Response باشد.

5. تفاوت Service با Model چیست؟

Model نماینده داده و جدول دیتابیس است، اما Service فرآیندهای تجاری و چندمرحله‌ای را مدیریت می‌کند. Model برای رابطه‌ها، Scopeها و رفتارهای نزدیک به داده مناسب است.

6. آیا همیشه باید از Service استفاده کنیم؟

خیر. برای عملیات ساده، استفاده مستقیم از Model در Controller قابل قبول است. اما برای منطق پیچیده، تکراری، چندمرحله‌ای یا قابل استفاده مجدد، Service انتخاب مناسبی است.

7. آیا Service باید Interface داشته باشد؟

همیشه نه. اما برای سرویس‌هایی که ممکن است چند پیاده‌سازی داشته باشند، مثل درگاه پرداخت، پیامک یا API خارجی، استفاده از Interface بسیار مفید است.

8. آیا Service می‌تواند از Model استفاده کند؟

بله. Service معمولاً با Modelها، Repositoryها، APIهای خارجی، Jobها و سایر سرویس‌ها تعامل دارد.

9. آیا Service باید خروجی JSON برگرداند؟

معمولاً خیر. خروجی JSON مربوط به Controller یا Resource است. Service بهتر است Model، Array، DTO، Boolean یا Result Object برگرداند.

10. چطور Service را تست کنیم؟

می‌توان Service را از Container دریافت کرد و متدهای آن را با داده‌های تستی اجرا کرد. اگر Service وابسته به API خارجی باشد، بهتر است وابستگی‌ها Mock شوند.

جمع‌بندی

Serviceها در لاراول یکی از مهم‌ترین ابزارهای معماری برای ساخت پروژه‌های تمیز، قابل توسعه و قابل تست هستند. اگرچه Laravel شما را مجبور به استفاده از Service Layer نمی‌کند، اما امکاناتی مانند Service Container، Dependency Injection و Service Provider باعث می‌شوند پیاده‌سازی Serviceها در لاراول بسیار طبیعی و قدرتمند باشد.

Serviceها کمک می‌کنند Controllerها سبک بمانند، Modelها بیش از حد سنگین نشوند و منطق تجاری برنامه در کلاس‌هایی مشخص و قابل نگهداری قرار بگیرد. این موضوع در پروژه‌های شرکتی، فروشگاهی، مالی، CRM، ERP، API محور و سیستم‌های چندبخشی اهمیت بسیار زیادی دارد.

نکته مهم این است که Service نباید بی‌دلیل به پروژه اضافه شود. هدف Service ساده‌سازی معماری است، نه پیچیده‌تر کردن آن. هرجا منطق برنامه تکراری، پیچیده، چندمرحله‌ای، وابسته به چند مدل یا نیازمند تست مستقل بود، Service می‌تواند انتخاب بسیار مناسبی باشد.

در یک پروژه حرفه‌ای Laravel، ترکیب Controllerهای سبک، Form Requestهای دقیق، Modelهای تمیز، Serviceهای متمرکز، Policyهای مشخص، Jobهای پس‌زمینه و Service Providerهای درست، پایه‌ای محکم برای توسعه بلندمدت محصول ایجاد می‌کند. 💼

CTA

اگر پروژه Laravel شما در حال رشد است و Controllerها یا Modelها بیش از حد شلوغ شده‌اند، زمان آن رسیده معماری نرم‌افزار را بازبینی کنید. تیم ما می‌تواند در طراحی Service Layer، بازطراحی Controllerهای سنگین، پیاده‌سازی معماری تمیز، بهینه‌سازی منطق تجاری، تست‌نویسی و استانداردسازی پروژه‌های Laravel به شما کمک کند. برای بررسی فنی پروژه لاراول خود، با ما تماس بگیرید و مسیر توسعه نرم‌افزار را حرفه‌ای‌تر، پایدارتر و مقیاس‌پذیرتر ادامه دهید. 🚀

منابع رسمی

  1. مستندات رسمی Service Container در Laravel 12؛ برای آشنایی با مدیریت وابستگی‌ها و Dependency Injection در لاراول.
    مطالعه مستندات رسمی Laravel درباره Service Container
  2. مستندات رسمی Service Providers در Laravel 12؛ برای یادگیری ثبت Bindingها و ساخت Providerهای سفارشی.
    مطالعه مستندات رسمی Laravel درباره Service Providers
  3. مستندات رسمی Queue در Laravel 12؛ برای اجرای عملیات زمان‌بر در پس‌زمینه و استفاده از Jobها.
    مطالعه مستندات رسمی Laravel درباره Queues
برچسب‌ها: Dependency Injection در لاراول Service ها در لاراول سرویس در لاراول Laravel Service Service Layer در Laravel آموزش Service در لاراول Laravel Service Class Service Container Service Provider معماری لاراول Laravel Clean Architecture Fat Controller Business Logic در لاراول تست Service در Laravel