Usar acciones en Laravel con Laravel Actions
Uno de mis paquetes favoritos, y que usé en mi primer directo, es Laravel Actions.
Qué es una acción en Laravel
El concepto de acción es muy conocido en el mundo de Laravel. A grandes rasgos, viene a ser una clase que hace una única cosa. Es decir, en lugar de tener el típico controlador ProductsController
con los métodos index
, show
, update
, store
y destroy
; crearíamos 5 controladores diferentes:
Products/ListProductsController
Products/ShowProductController
Products/UpdateProductController
Products/StoreProductController
Products/DestroyProductController
Esto está además escrito en la documentación de Laravel como Single Action Controllers.
Por ejemplo, si estuviéramos usando los SAC nuestro código para listar los productos se quedaría algo así:
// app/Http/Controllers/Products/ListProductsController
<?php
namespace App\Http\Controllers\Products;
use App\Models\Product;
class ListProductsController extends Controller
{
public function __invoke()
{
return response()->json([
'products' => Product::orderBy('name')->paginate(15),
]);
}
}
// api.php
Route::get('products', ListProductsController::class);
Si esto ya está soportado en Laravel, ¿por qué necesitamos una librería?
Qué es Laravel Actions
Pues bien, Laravel Actions no deja de ser un wrapper alrededor de los Single Action Controllers (SAC), añadiéndoles más funcionalidades bastante útiles. Nos permite escribir código más atómico en nuestra aplicación, que será después reutilizable desde muchas partes. La misma action puede usarse desde otra parte del código, como Job, como Command, como Listener o como Controller.
Imaginemos que tenemos una acción para enviar un e-mail a un usuario tras un pedido. Nuestro Laravel Action sería algo así:
<?php
namespace App\Actions\Orders;
use App\Models\Order;
use Lorisleiva\Actions\Concerns\AsAction;
// ...
class NotifyOrderToUser
{
use AsAction;
public function handle(Order $order)
{
Mail::to($order->user->email)->send(new OrderNotificationMail($order));
// send a copy to the admin
Mail::to(config('myapp.emails.admin'))->send(new OrderNotificationMail($order));
}
}
Desde cualquier otra parte de nuestro código, podríamos ejecutar lo siguiente:
<?php
namespace App\Http\Controllers\Products;
use App\Models\Product;
use App\Actions\Orders\NotifyOrderToUser;
class OrderController extends Controller
{
public function store()
{
// ...
// process
// ...
NotifyOrderToUser::run($order);
// ...
}
}
Ejecutar el ::run()
ejecutaría el código del método handle()
de forma síncrona. Si queremos usar esa misma acción y mandarla a la cola de procesos para ejecutarlo de forma asíncrona solamente hay que cambiar el ::run
a ::dispatch
:
<?php
namespace App\Http\Controllers\Products;
use App\Models\Product;
use App\Actions\Orders\NotifyOrderToUser;
class OrderController extends Controller
{
public function store()
{
// ...
// process
// ...
NotifyOrderToUser::dispatch($order);
// ...
}
}
Vamos a por otra ventaja: Si quisiéramos poder usar este action también desde la línea de comandos para poder re-enviar el e-mail manualmente (previo registro de los comandos de las actions):
<?php
namespace App\Actions\Orders;
use App\Models\Order;
use Illuminate\Console\Command;
use Lorisleiva\Actions\Concerns\AsAction;
// ...
class NotifyOrderToUser
{
use AsAction;
public string $commandSignature = 'order:notify {orderId}';
public function handle(Order $order)
{
Mail::to($order->user->email)->send(new OrderNotificationMail($order));
// send a copy to the admin
Mail::to(config('myapp.emails.admin'))->send(new OrderNotificationMail($order));
}
public function asCommand(Command $command)
{
$this->handle(Order::findOrFail($command->argument('orderId')));
$command->info('Sent!');
}
}
Ahora, la misma acción podemos ejecutarla desde cualquier parte del código ty también desde artisan de la siguiente forma:
php artisan order:notify 1
También podemos usar los métodos asController
, asJob
y asListener
para ejecutar la acción en cualquiera de estas situaciones.
Además, tiene una funcionalidad de mocking muy útil a la hora de hacer tests.
Gracias a esta librería estamos produciendo un código mucho más modular y testeable que podemos reutilizar en diferentes proyectos en nuestra empresa 🎉