DEV Community

Cover image for Leveraging Laravel Actions for Cleaner, Reusable Code
Alex Beygi
Alex Beygi

Posted on

Leveraging Laravel Actions for Cleaner, Reusable Code

Laravel Actions are a design pattern used to encapsulate specific units of work—such as creating a user, sending an email, or processing a queue—into individual, reusable classes. Instead of bloating your controller or service classes, Actions help you isolate the logic into small, single-purpose files.

Think of an Action as a method that does one thing and does it well, but it's wrapped inside a class to promote clarity, testability, and reuse.

Why Use Laravel Actions?

Using Actions in Laravel can greatly enhance your codebase. Here’s why:

1. Separation of Concerns
Instead of having bloated controllers or services with mixed responsibilities, each Action handles a single task.

Example: You can move the logic for creating a user from your controller into a CreateUserAction class.

2. Reusability
Actions can be used across controllers, jobs, commands, or even tests without duplicating logic.

Example: The same CreateInvoiceAction can be used in a web controller and a scheduled command.

3. Testability
Since Actions are standalone classes, they can be easily tested in isolation without relying on the full controller or service environment.

Example: You can write a unit test for SendWelcomeEmailAction without mocking any controller behaviour.

4. Cleaner Codebase
Controllers become thin and focused on request/response handling. Business logic lives in Actions.

Example: A controller method becomes just 3 lines: validate, call action, return response.

Installing Laravel Actions (by Loris Leiva)

You can build Actions manually, but the lorisleiva/laravel-actions package makes it much more elegant.

composer require lorisleiva/laravel-actions

Practical Examples:

Example 1: Action for Validation and Creating a Post

app/Actions/Post/CreatePostAction.php

namespace App\Actions\Post;

use App\Models\Post;
use Lorisleiva\Actions\Concerns\AsAction;

class CreatePost
{
    /**
     * Handle the creation of a new post.
     */
    public function handle(array $data): Post
    {
        return Post::create($data);
    }

    /**
     * Validation rules for the request.
     */
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ];
    }

    /**
     * Invoke method used by Laravel's route to execute the action.
     */
    public function __invoke(Request $request): Post
    {
        $validated = $request->validate($this->rules());
        return $this->handle($validated);
    }
}
Enter fullscreen mode Exit fullscreen mode

Route Example

use App\Actions\Post\CreatePost;

Route::post('/posts', CreatePost::class);
Enter fullscreen mode Exit fullscreen mode

Example 2: Action for Running a Queue Job

// app/Actions/User/SendWelcomeEmailAction.php

namespace App\Actions\User;

use App\Jobs\SendWelcomeEmail;

class SendWelcomeEmailAction
{
    public function execute($user)
    {
        SendWelcomeEmail::dispatch($user);
    }
}
Usage in Controller or Listener:

$action = app(SendWelcomeEmailAction::class);
$action->execute($user);
Enter fullscreen mode Exit fullscreen mode

How to Implement Actions

There are multiple ways to structure your Actions:
Manually create them in a Actions namespace.

Example using the package:

php artisan make:action CreateUser

And then:

public function handle(array $data)
{
    // Business logic
}
Enter fullscreen mode Exit fullscreen mode

When to Use Laravel Actions?

Use Actions when:

  • A piece of logic is repeated across different parts of your application.
  • Your controller methods are growing too large.
  • You want to improve unit testing and readability.
  • You are building a service-oriented or domain-driven architecture.

Conclusion

Laravel Actions are a powerful tool to improve the architecture of your Laravel application. They promote clean code, reusability, and testability, while reducing the cognitive load in your controllers. Whether you're validating requests, running queues, or performing business logic, wrapping that logic in Actions will lead to a more maintainable and scalable application.

Top comments (3)

Collapse
 
xwero profile image
david duymelinck

While I like the novelty of the idea behind the library, I think it is the wrong abstraction.

From the readme;

class PublishANewArticle
{
    use AsAction;

    public function handle(User $author, string $title, string $body): Article
    {
        return $author->articles()->create([
            'title' => $title,
            'body' => $body,
        ]);
    }

    public function asController(Request $request): ArticleResource
    {
        $article = $this->handle(
            $request->user(),
            $request->get('title'),
            $request->get('body'),
        );

        return new ArticleResource($article);
    }

    public function asListener(NewProductReleased $event): void
    {
        $this->handle(
            $event->product->manager,
            $event->product->name . ' Released!',
            $event->product->description,
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

The reason we have Controllers, listeners and repositories is to create a loose coupling.
The action in this example has tight coupling because the controller and listener are tied to the handle method.

In the Createpost example the rules method is tied to the asController method. But it means nothing for an asListener method if that is added.

I also find Route::post('/posts', CreatePost::class); confusing, because I would look for an __invoke method in that class.

I think the larger the application gets the harder it is going to be to use actions, because almost everything is an action.

Collapse
 
alex-beygi profile image
Alex Beygi

Thanks you for you comment.
I totally get your concerns, and they’re valid — especially around coupling and scaling clarity.
That said, the Laravel Actions pattern offers a different way of organising logic that can still maintain separation of concerns, if used intentionally.

This is a fair concern. The key is to use Actions to encapsulate business logic, not everything. I avoid putting trivial or CRUD-only operations in actions unless they encapsulate domain-specific behaviour or are reused elsewhere.

In large applications, actions help maintain flat controller files, promote reusability, and simplify testing, especially when business logic isn't scattered across multiple places.

And last part is, thanks for the great eye and thoughtful feedback. I've updated the code to reflect your points.

Collapse
 
xwero profile image
david duymelinck

When I first saw the word action I thought of the Action-Domain-Responder pattern. And it is partially right.

But after seeing the documentation I'm more leaning to use cases as the main pattern that the library is build on.

The biggest problem I have is that an Action is a jack-of-all-trades object.