Understanding Action Patterns: What They Are and How to Use Them

24.6.2025

laravel action patterns design patterns TIL
action pattern

A Design pattern is a general solution to a common problem that occurs frequently in software design. It is a reusable solution that can be applied to different situations.

I got into design patterns mainly because my controller in Laravel was getting too big and I didn’t know where I should put the logic, and I saw that in Laravel community people are talking a lot about the action pattern (another name for it is command pattern btw)

So what is it exactly?

In order to see how it works, let’s create a simple example.

We have a controller that has a method that creates a user (in app/Http/Controllers/UserController.php)

class UserController extends Controller
{
    public function store(Request $request)
    {
        // Validate the incoming request data
        $validatedData = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ]);

        // Create the user with the validated data
        $user = User::create($validatedData);

        // Send a welcome email to the new user
        Mail::to($user->email)->queue(new WelcomeMail($user));

        // Log the creation of the new user
        Log::info('New user created: ' . $user->email);

        // Redirect the user to the homepage
        return redirect()->route('home');
    }
}

Still many other steps can happen during user creation, like sending admin notification, sending SMS (e.g. for phone verification), etc. But for the sake of simplicity, we will only focus on the user creation.

Generate formRequest

I also moved the validation logic to the request class using php artisan make:request CreateUserRequest which is located in app/Http/Requests/CreateUserRequest.php and it will look like this:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreateUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ];
    }
}

Create the action

There is no artisan command to create action pattern but you can use php artisan make:class Action/UserCreateAction to create a new action.

class UserCreateAction
{
    public function execute(array $data): void
    {
        // Create the user with the validated data
        $user = User::create($data);

        // Send a welcome email to the new user
        Mail::to($user->email)->queue(new WelcomeMail($user));

        // Log the creation of the new user
        Log::info('New user created: ' . $user->email);
    }
}

Here as you can see, we haven’t returned anything and that’s fine but there is no hard rule for this. Sometimes you might want to return something, like a success message or the user itself.

Like for example if you look the open source example like ServerCheck.php in Coolify it does return something and in some other action it don’t like DeleteServer.php , so that’s the decision you have to make.

Also, from what I have seen, some people just name the method inside of the action class as execute or handle. I much prefer execute for action and handle just for queue jobs, but again if you see the project like Coolify you can see that author just used handle.

So now our controller will look like this:

class UserController extends Controller
{
    public function store(CreateUserRequest $request, UserCreateAction $userCreateAction)
    {
        $userCreateAction->execute($request->validated());

        // Redirect the user to the homepage
        return redirect()->route('home');
    }
}

Summary

From my perspective, the whole point of actions is to move logic out of the controller and into a separate class and it would make it way easier to test the logic using the mighty framework called pest. That’s for another blog post.