Escribe código limpio y mantenible con el patrón Action
Herminio Heredia
hace 1 mes
¿Cansado de ver controladores inflados y lógica de negocio esparcida por todo tu proyecto Laravel?
Todos hemos estado ahí.
Al principio, todo parece ir bien, pero a medida que la aplicación crece, el código se vuelve un laberinto inmanejable. Clases con cientos de líneas, métodos que hacen de todo… un verdadero desastre.
Aquí es donde entra el patrón Action. Como un chef que organiza sus ingredientes antes de cocinar, este patrón te permite encapsular la lógica de tu aplicación en unidades pequeñas, reutilizables y fáciles de mantener.
En este tutorial, te guiaré paso a paso para que domines el patrón Action en Laravel 11. No solo aprenderás la teoría, sino que también construiremos un ejemplo práctico y escribiremos pruebas para garantizar que nuestro código funcione como un reloj.
¿Qué es el Patrón Action y por qué deberías usarlo?
Imagina que estás construyendo una tienda online. Tienes acciones como "agregar producto al carrito", "procesar el pago", "enviar email de confirmación", etc. Cada una de estas acciones puede involucrar varios pasos y componentes.
El patrón Action te permite encapsular cada una de estas acciones en una clase independiente. En lugar de tener un controlador gigante que maneje todo, delegas la responsabilidad a estas clases "Action".
Beneficios:
- Código más limpio y organizado: Adiós al código espagueti.
- Mayor mantenibilidad: Las acciones son fáciles de entender, modificar y extender.
- Reusabilidad: Puedes usar la misma acción en diferentes partes de tu aplicación.
- Pruebas más sencillas: Al aislar la lógica en unidades pequeñas, es más fácil escribir pruebas unitarias.
- Mejor colaboración en equipo: Cada desarrollador puede trabajar en acciones específicas sin pisarse los pies.
Implementando el Patrón Action en Laravel: Un ejemplo práctico
Vamos a construir un sistema simple para gestionar tareas. Nuestro ejemplo tendrá las siguientes acciones:
- Crear una nueva tarea.
- Marcar una tarea como completada.
- Eliminar una tarea.
Paso 1: Crear las clases Action
Primero, crearemos un directorio app/Actions
para almacenar nuestras clases Action. Dentro de este directorio, crearemos tres archivos: CreateTask.php
, CompleteTask.php
y DeleteTask.php
.
app/Actions/CreateTask.php
<?php
namespace App\Actions;
use App\Models\Task;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateTask
{
/**
* Crea una nueva tarea.
*
* @param array $input
* @return Task
* @throws ValidationException
*/
public function execute(array $input): Task
{
$validator = Validator::make($input, [
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
return Task::create($validator->validated());
}
}
app/Actions/CompleteTask.php
<?php
namespace App\Actions;
use App\Models\Task;
class CompleteTask
{
/**
* Marca una tarea como completada.
*
* @param Task $task
* @return void
*/
public function execute(Task $task): void
{
$task->update(['completed' => true]);
}
}
app/Actions/DeleteTask.php
<?php
namespace App\Actions;
use App\Models\Task;
class DeleteTask
{
/**
* Elimina una tarea.
*
* @param Task $task
* @return void
*/
public function execute(Task $task): void
{
$task->delete();
}
}
Paso 2: Invocar las acciones desde el controlador
Ahora, modificaremos nuestro controlador TaskController
para que use las clases Action.
<?php
namespace App\Http\Controllers;
use App\Actions\CompleteTask;
use App\Actions\CreateTask;
use App\Actions\DeleteTask;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function store(Request $request, CreateTask $createTask)
{
try {
$task = $createTask->execute($request->all());
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->errors());
}
return redirect()->route('tasks.index')->with('success', 'Tarea creada correctamente.');
}
public function complete(Task $task, CompleteTask $completeTask)
{
$completeTask->execute($task);
return redirect()->route('tasks.index')->with('success', 'Tarea marcada como completada.');
}
public function destroy(Task $task, DeleteTask $deleteTask)
{
$deleteTask->execute($task);
return redirect()->route('tasks.index')->with('success', 'Tarea eliminada correctamente.');
}
}
Paso 3: Escribir pruebas unitarias
Para asegurarnos de que nuestras acciones funcionan correctamente, escribiremos algunas pruebas unitarias.
<?php
namespace Tests\Feature;
use App\Actions\CreateTask;
use App\Models\Task;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class CreateTaskTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function can_create_a_task()
{
$createTask = new CreateTask();
$task = $createTask->execute([
'title' => 'Nueva tarea',
'description' => 'Descripción de la tarea',
]);
$this->assertInstanceOf(Task::class, $task);
$this->assertEquals('Nueva tarea', $task->title);
$this->assertEquals('Descripción de la tarea', $task->description);
}
/** @test */
public function title_is_required()
{
$createTask = new CreateTask();
$this->expectException(\Illuminate\Validation\ValidationException::class);
$createTask->execute([
'description' => 'Descripción de la tarea',
]);
}
}
Profundizando en el Patrón Action: Inyección de Dependencias y más
El patrón Action no se limita a simples clases con un método execute
. Puedes aprovechar la inyección de dependencias de Laravel para hacer tus acciones aún más flexibles y poderosas.
Inyección de dependencias:
Supongamos que necesitas enviar una notificación al usuario cuando se crea una nueva tarea. En lugar de instanciar la clase de notificación dentro de la acción, puedes inyectarla en el constructor.
<?php
namespace App\Actions;
use App\Models\Task;
use App\Notifications\TaskCreated;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateTask
{
/**
* @param \App\Notifications\TaskCreated $notification
*/
public function __construct(private TaskCreated $notification)
{
//
}
/**
* Crea una nueva tarea.
*
* @param array $input
* @return Task
* @throws ValidationException
*/
public function execute(array $input): Task
{
// ... (validación)
$task = Task::create($validator->validated());
Notification::send($task->user, $this->notification);
return $task;
}
}
Manejo de errores:
Puedes usar excepciones personalizadas para manejar errores específicos dentro de tus acciones. Esto te permite tener un control más granular sobre el flujo de la aplicación.
Organización en subdirectorios:
A medida que tu aplicación crece, puedes organizar tus acciones en subdirectorios dentro de app/Actions
. Por ejemplo, podrías tener app/Actions/Tasks
, app/Actions/Users
, etc.
Más allá de lo básico: Patrones de diseño y el patrón Action
El patrón Action se puede combinar con otros patrones de diseño para crear soluciones aún más elegantes.
Chain of Responsibility:
Si tienes una acción que involucra varios pasos, puedes usar el patrón Chain of Responsibility para encadenar varias acciones. Cada acción en la cadena se encarga de un paso específico.
Command:
El patrón Command es similar al patrón Action, pero se enfoca en representar una operación como un objeto. Esto te permite, por ejemplo, poner acciones en cola para ejecutarlas de forma asíncrona.
Conclusión: Eleva tu código Laravel con el patrón Action
El patrón Action es una herramienta poderosa para escribir código Laravel limpio, mantenible y escalable. Al encapsular la lógica de tu aplicación en unidades pequeñas y reutilizables, puedes simplificar el desarrollo y mejorar la calidad de tu código.
¡No esperes más! Empieza a implementar el patrón Action en tus proyectos Laravel y experimenta los beneficios por ti mismo. Recuerda que la práctica hace al maestro, así que no dudes en experimentar y explorar diferentes formas de usar este patrón.