Laractions

Laractions gives every meaningful operation in your app its own class: one handle() method, one job, called the same way from a controller, a job, a console command or a test.
Installation
composer require edulazaro/laractions
The service provider auto-registers and there is nothing to publish to start writing actions. Tracing is the one feature that needs a migration; see below.
Writing an action
php artisan make:action SendWelcomeEmail
The generator drops the Action suffix on purpose: the App\Actions namespace already says what the class is.
namespace App\Actions;
use EduLazaro\Laractions\Action;
class SendWelcomeEmail extends Action
{
public function handle(string $email, string $subject): void
{
// send the mail...
}
}
Calling it
You never new an action. create() resolves the instance through the container, so constructor dependencies are injected; run() executes handle().
SendWelcomeEmail::create()->run('user@example.com', 'Welcome');
create() takes the constructor arguments, run() takes the handle() arguments.
Passing arguments
run() maps to handle() by reflection, so the same handle(string $email, string $subject) accepts all three forms:
$action->run('user@example.com', 'Welcome'); // positional
$action->run(email: 'user@example.com', subject: 'Welcome'); // named
$action->run(['email' => 'user@example.com', 'subject' => 'Welcome']); // keyed by parameter name
One rule worth knowing so it never surprises you: when handle() declares a single array parameter, an array passed to run() goes through whole as that argument rather than being spread. A concrete typed parameter such as handle(File $file) still receives the value itself. It mirrors how PHP hands an array to fn(array $x) versus fn(File $x).
Model-scoped actions
An action often belongs to a record. Generate one bound to a model and it gains a typed property for it.
php artisan make:action SendWelcomeEmail --model=User
namespace App\Actions\User;
class SendWelcomeEmail extends Action
{
protected User $user;
public function handle(): void
{
Mail::to($this->user->email)->send(new WelcomeEmail());
}
}
Add HasActions to the model and register the actions it owns, keyed by a short name.
class User extends Model
{
use HasActions;
protected array $actions = ['send_welcome' => SendWelcomeEmail::class];
}
$user->action('send_welcome')->run();
$user->action(SendWelcomeEmail::class)->run(); // or by class, no registration
The binding walks the model's class and its parents looking for a property whose name matches the model, which is why a plain protected User $user; is all the wiring needed.
Validation
A $rules array on the action validates the resolved arguments before handle() runs, throwing a ValidationException on failure. An action guards its own inputs even when called from a command or a test that never touched an HTTP request.
Actors and tracing
Any model becomes an actor with the IsActor trait:
$user->act(SomeAction::class)->on($target);
trace() writes an action_traces row with the actor, the target and the params. This is the only part of the package that needs a migration, and it is not loaded automatically:
php artisan vendor:publish --tag=laractions-migrations
Queueing an action
Call dispatch() instead of run() and the action is wrapped in a job, with queue(), delay() and retry() to configure it.
SendWelcomeEmail::create()
->queue('emails')
->dispatch('user@example.com', 'Welcome');
built and maintained by Edu Lazaro · MIT license