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
| Service | Action |
|---|---|
| گروه متدهای مرتبط domain | یک متد = یک use case |
| PostService با create/update/delete | CreatePostAction جدا |
| برای 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.