مقاله

Action Classes در Laravel — یک کلاس، یک کار، کد تمیز

Action Classes در Laravel — یک کلاس، یک کار، کد تمیز

Single Action Class برای use caseهای مشخص — جایگزین Service چاق و Controller شلوغ با invokable و handle.

Service با سی متد نامرتبط و Controller شلوغ آشناست. Action Class یک الگوی سازمان‌دهی کد است: هر use case یک کلاس با مسئولیت باریک. CreatePostAction فقط ایجاد می‌کند؛ PublishPostAction فقط انتشار. Laravel کلاس پایه رسمی «Action» ندارد و این الگو اجباری فریمورک نیست — community (از جمله پکیج‌های اختیاری مثل lorisleiva/actions یا کلاس دستی با handle) آن را رایج کرده است.

اگر بین Service Layer و Action مردد هستید، تصمیم‌گیری مقایسه‌ای در Service در مقابل Action است. این مقاله روی پیاده‌سازی، ورودی/خروجی، تست و نقاط فراخوانی تمرکز دارد.

یک عملیات کسب‌وکار در یک کلاس

namespace App\Actions\Posts;

class CreatePostAction
{
    public function __construct(
        private PostRepositoryInterface $posts,
    ) {}

    public function handle(array $data, User $author): Post
    {
        return DB::transaction(function () use ($data, $author) {
            $post = $this->posts->create([
                ...$data,
                'user_id' => $author->id,
                'status' => 'draft',
            ]);

            event(new PostCreated($post));

            return $post;
        });
    }
}

وابستگی‌ها از constructor می‌آیند؛ خروجی صریح است؛ تراکنش و event داخل همان use case می‌مانند — نه پخش‌شده در Controller. جزئیات تراکنش: Database Transaction.

فراخوانی از Controller

public function store(
    StorePostRequest $request,
    CreatePostAction $action,
) {
    $post = $action->handle($request->validated(), $request->user());

    return new PostResource($post);
}

Controller نازک می‌ماند — اعتبارسنجی در FormRequest، مجوز در Policy، منطق در Action. این هم‌راستا با SRP است.

__invoke برای route مستقیم

class PublishPostAction
{
    public function __invoke(Post $post): Post
    {
        $post->update([
            'status' => 'published',
            'published_at' => now(),
        ]);

        return $post->fresh();
    }
}

// Route::post('/posts/{post}/publish', PublishPostAction::class);

Laravel کلاس invokable را مثل Controller resolve می‌کند. برای use caseهای خیلی کوچک کافی است؛ برای منطق سنگین‌تر معمولاً handle خواناتر است.

ورودی، خروجی و وابستگی

  • ورودی: array معتبر، FormRequest->validated()، یا DTO — DTO
  • خروجی: Model، DTO، void یا Result object — typehint صریح بگذارید
  • وابستگی: repository، gateway، clock — از container؛ static helper کمتر
readonly class CreatePostData
{
    public function __construct(
        public string $title,
        public string $slug,
        public string $content,
    ) {}
}

public function handle(CreatePostData $data, User $author): Post

نقاط ورود دیگر: Job و Command

Action منطق sync است؛ Job/Command نقطه ورود async یا CLI. Job نباید دوباره business را کپی کند — Action را صدا بزند. راهنمای dispatch: Jobs و dispatch.

class SendNewsletterJob implements ShouldQueue
{
    public function handle(SendNewsletterAction $action): void
    {
        $action->handle();
    }
}

رابطه با Service — بدون تکرار مقایسه کامل

Service معمولاً چند متد مرتبط domain را گروه می‌کند؛ Action یک use case را جدا می‌کند. هر دو می‌توانند هم‌زیستی داشته باشند (مثلاً Service نازک چند Action را هماهنگ کند). جدول تصمیم و anti-patternها را در مقاله مقایسه ببینید، نه اینجا.

تست Action

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class CreatePostActionTest extends TestCase
{
    use RefreshDatabase;

    public function test_creates_post_for_author(): void
    {
        $user = User::factory()->create();
        $action = $this->app->make(CreatePostAction::class);

        $post = $action->handle([
            'title' => 'Test',
            'slug' => 'test',
            'content' => 'x',
        ], $user);

        $this->assertSame($user->id, $post->user_id);
    }
}

Unit با mock repository برای منطق خالص؛ Feature از HTTP برای wiring — Feature test و تست Repository و Action.

نام‌گذاری و پوشه

app/Actions/Posts/CreatePostAction.php
app/Actions/Posts/PublishPostAction.php

فعل + اسم + پسوند Action؛ یک convention در تیم مهم‌تر از نام پکیج است.

چه وقت Action اضافه است؟

  • CRUD یک‌خطی بدون قانون کسب‌وکار — لایه اضافه فقط نویز است
  • کلاس فقط متدهای یک Service را یک‌به‌یک کپی کرده بدون مرز روشن
  • هنوز FormRequest و Policy جا نیفتاده و Action تبدیل به «Controller دوم» شده

وقتی چند مرحله، تراکنش، event یا چند نقطه ورود دارید، Action معمولاً ارزش دارد.

جمع‌بندی

Action Class ابزار اجباری Laravel نیست؛ الگوی سازمان‌دهی برای use caseهای مشخص است. آن را باریک نگه دارید، از DI استفاده کنید، از Controller/Job/Command صدا بزنید و قبل از پخش کردن ده‌ها Action بی‌دلیل، با مقاله Service در مقابل Action مرز را روشن کنید. الگوهای کمکی: Creational، Behavioral، Repository.

مشاوره معماری Laravel

اشتراک‌گذاری