Absortio

Email → Summary → Bookmark → Email

Action Pattern in Laravel: Concept, Benefits, Best Practices

Extracto

The Laravel Action Pattern organizes business logic into reusable, testable classes. It enables context-independent actions for maintainable code.

Resumen

Resumen Principal

El Patrón de Acción (Action Pattern) en Laravel se presenta como una estrategia arquitectónica fundamental para encapsular una unidad de lógica única dentro de una clase dedicada. Este enfoque promueve la creación de código más limpio, reutilizable y fácilmente testeable. Al confinar la lógica de negocio específica a una clase con un único método público, comúnmente denominado handle(), y un constructor para inyectar dependencias estrictamente necesarias, se evita la proliferación de lógica compleja en controladores, comandos o trabajos, manteniéndolos ligeros y enfocados. Sus principales beneficios radican en la reutilización sin dependencia del contexto, permitiendo invocar la misma acción desde cualquier punto de la aplicación; una inyección de dependencias precisa que elimina el desorden en los constructores; y una facilidad superior para el testing aislado, ya que cada acción representa una unidad de trabajo bien definida. La organización bajo app/Actions y el cumplimiento de convenciones de nombrado refuerzan la mantenibilidad y la escalabilidad del proyecto.

Elementos Clave

  • Reusabilidad Contexto-Independiente: Una de las mayores ventajas del Patrón de Acción es su capacidad de ser invocado desde cualquier punto de la aplicación, ya sean controladores, trabajos en cola, comandos de consola, pruebas unitarias o incluso escuchadores de eventos. Esto elimina la duplicación de código y centraliza la lógica de negocio, permitiendo que una misma implementación, como CreateUser, sirva para múltiples flujos sin necesidad de adaptaciones contextuales, lo que optimiza el desarrollo y mantenimiento.

  • Inyección de Dependencias Enfocada y Precisa: A diferencia de las clases de servicio que pueden agrupar múltiples métodos y, por ende, requerir una amplia gama de dependencias en su constructor, una clase de acción está diseñada para una única tarea. Esto asegura que su constructor solo inyecte las dependencias estrictamente necesarias para esa operación específica. El resultado son constructores concisos y con un propósito claro, evitando la saturación de dependencias irrelevantes y mejorando la legibilidad y gestión de la clase.

  • Facilidad de Pruebas en Aislamiento: Dado que una acción encapsula una unidad atómica de trabajo, su diseño intrínsecamente facilita las pruebas unitarias. Los desarrolladores pueden instanciar la clase de acción directamente y llamar a su método handle(), probando la lógica de negocio de manera aislada, sin la necesidad de inicializar todo el stack HTTP o complicadas configuraciones de entorno. Esto simplifica la creación de tests

Contenido

What is the Action Pattern in Laravel?

The Action Pattern is a simple architectural pattern used in Laravel and other PHP applications to encapsulate a single unit of logic inside a dedicated class. Typically, an action class has:

  • A single public method most commonly named handle() that contains the core logic.
  • A constructor that injects any required dependencies (e.g., other actions, services, repositories).

This pattern promotes cleaner, reusable, and more testable code. Action classes are generally placed under the app/Actions directory and are used to keep controllers, commands, and jobs slim and focused.

namespace App\Actions;

use App\Models\User;

class CreateUser

{

public function handle(array $data): User

{

return User::create($data);

}

}

Benefits of the Action Pattern

1. Write Once, Call Anywhere (Context-Independent)

An action can be called from any context: controllers, jobs, console commands, tests, event listeners, etc.

// In a controller

class UserController extends Controller

{

public function store(Request $request, CreateUser $createUser)

{

$user = $createUser->handle($request->validated());

return response()->json($user);

}

}

// In a job/command

public function handle(CreateUser $createUser)

{

$createUser->handle($this->data);

}

2. No Irrelevant Dependency Injections

Unlike service classes with multiple unrelated methods, action classes are focused. Each class handles one task and receives only the dependencies it needs. This results in constructors that are concise and purposeful. With this approach, you avoid having a constructor filled with many injected dependencies that may only be used by a few methods.

Unlike service classes with multiple unrelated methods, action classes are focused. Each class handles one task and receives only the dependencies it needs. This results in constructors that are concise and purposeful.

3. Easier to Test in Isolation

Since an action encapsulates a single unit of work, it becomes easier to test independently of the HTTP layer or other services.

public function test_it_creates_a_user()

{

$action = new CreateUser();

$user = $action->handle([

'name' => 'Jane Doe',

'email' => '[email protected]',

'password' => bcrypt('password'),

]);

$this->assertDatabaseHas('users', [

'email' => '[email protected]',

]);

$this->assertEquals('Jane Doe', $user->name);

}

Best Practices

1. Structure: Place actions under app/Actions

Organize your action classes in the app/Actions directory for better discoverability.

app/

└── Actions/

├── CreateUser.php

├── UpdateUser.php

2. Naming Convention: {Action}{Resource}.php

Use a clear and consistent naming convention such as CreateUser, UpdateProfile, or DeletePost.

3. Method Name: Prefer handle()

Stick with handle() for consistency with Laravel's jobs and listeners. Alternatives include __invoke, execute(), or run() but only if your team prefers a different standard.

public function handle(array $data): User

4. Method Parameters

Pass in either the resource and data, or just the data, depending on the use case.

// For updating existing resource

public function handle(User $user, array $data): User

// For creating new resource

public function handle(array $data): User

5. Wrap Logic in DB::transaction()

If your action performs multiple operations or calls other actions, wrap the logic inside a transaction to ensure consistency.

public function __construct(

public SyncUserRoles $syncUserRoles,

public AssignTeam $assignTeam,

) {}

public function handle(array $data): User

{

return DB::transaction(function () use ($data) {

$user = User::create($data);

// injected actions

$this->syncUserRoles->handle($user, $data['roles']);

$this->assignTeam->handle($user);

return $user;

});

}

6. Broadcast Events When Necessary

If your action modifies a resource and you want to notify the frontend (e.g., via Reverb or Inertia), broadcast events.

use App\Events\UserCreated;

broadcast(new UserCreated($user))->toOthers();

7. Return the Resource

Return the modified or created resource from your action.

return $user;

8. Inject Other Actions When Needed

Action classes can depend on other actions to compose complex behavior.

public function __construct(

protected SyncUserRoles $syncUserRoles,

protected AssignTeam $assignTeam,

) {}

More Resources