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