مقاله

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

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

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

Service با ۳۰ متد unrelated، Controller که همه چیز را می‌داند — آشناست؟ Action Class می‌گوید: هر use case یک کلاس. CreatePostAction فقط post می‌سازد؛ PublishPostAction فقط publish. Laravel رسمی package ندارد اما الگو در community (Lorisleiva/actions و سبک دستی) رایج است.

ساختار Action

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;
        });
    }
}

استفاده در Controller

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

    return new PostResource($post);
}

Controller ۵ خط — SRP.

__invoke برای invokable

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 route به invokable controller/action resolve می‌کند.

Action vs Service

ServiceAction
گروه متدهای مرتبط domainیک متد = یک use case
PostService با create/update/deleteCreatePostAction جدا
برای module کوچک OKبرای feature زیاد scale بهتر

Action vs Job

Job برای async/queue — Jobs. Action sync business logic؛ Job می‌تواند Action را صدا بزند.

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

Input با DTO (اختیاری)

readonly class CreatePostData
{
    public function __construct(
        public string $title,
        public string $slug,
        public string $content,
    ) {}
}

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

تست Action

it('creates post', function () {
    $user = User::factory()->create();
    $action = app(CreatePostAction::class);

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

    expect($post->user_id)->toBe($user->id);
});

Feature test از HTTP؛ unit مستقیم روی Action.

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

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

verb + noun + Action — یکسان در تیم.

کی Action نسازیم؟

CRUD یک خط در Filament admin — Action اضافه. منطق چند مرحله‌ای، event، transaction — Action مناسب.

جمع‌بندی

Action = use case executable. با Repository و SOLID کنار هم layer تمیز می‌سازد. Patterns: Creational، Behavioral.

action-based architecture