# Table of Contents - [Laravel Actions](#laravel-actions) - [Installation | Laravel Actions](#installation-laravel-actions) - [Laravel Actions](#laravel-actions) - [Basic usage | Laravel Actions](#basic-usage-laravel-actions) - [Generate reservation code | Laravel Actions](#generate-reservation-code-laravel-actions) - [Get user profile | Laravel Actions](#get-user-profile-laravel-actions) - [Upgrade from 1.x | Laravel Actions](#upgrade-from-1-x-laravel-actions) - [Create new article | Laravel Actions](#create-new-article-laravel-actions) - [Export user data | Laravel Actions](#export-user-data-laravel-actions) - [Demote team membership | Laravel Actions](#demote-team-membership-laravel-actions) - [One class, one task | Laravel Actions](#one-class-one-task-laravel-actions) - [Synchronize contacts from Google | Laravel Actions](#synchronize-contacts-from-google-laravel-actions) - [Register your task as a controller | Laravel Actions](#register-your-task-as-a-controller-laravel-actions) - [Add validation to your controllers | Laravel Actions](#add-validation-to-your-controllers-laravel-actions) - [Mock and test your actions | Laravel Actions](#mock-and-test-your-actions-laravel-actions) - [More granular traits | Laravel Actions](#more-granular-traits-laravel-actions) - [Dispatch asynchronous jobs | Laravel Actions](#dispatch-asynchronous-jobs-laravel-actions) - [How does it work? | Laravel Actions](#how-does-it-work-laravel-actions) - [Listen for events | Laravel Actions](#listen-for-events-laravel-actions) - [Execute as commands | Laravel Actions](#execute-as-commands-laravel-actions) - [Use unified attributes | Laravel Actions](#use-unified-attributes-laravel-actions) - [As object | Laravel Actions](#as-object-laravel-actions) - [As controller | Laravel Actions](#as-controller-laravel-actions) - [As listener | Laravel Actions](#as-listener-laravel-actions) - [As command | Laravel Actions](#as-command-laravel-actions) - [As job | Laravel Actions](#as-job-laravel-actions) - [As fake | Laravel Actions](#as-fake-laravel-actions) - [With attributes | Laravel Actions](#with-attributes-laravel-actions) - [Laravel Actions](#laravel-actions) - [Laravel Actions](#laravel-actions) - [Laravel Actions](#laravel-actions) - [Installation | Laravel Actions](#installation-laravel-actions) - [Basic usage | Laravel Actions](#basic-usage-laravel-actions) - [Laravel Actions](#laravel-actions) - [Actions' attributes | Laravel Actions](#actions-attributes-laravel-actions) - [Dependency injections | Laravel Actions](#dependency-injections-laravel-actions) - [Validation | Laravel Actions](#validation-laravel-actions) - [Actions as objects | Laravel Actions](#actions-as-objects-laravel-actions) - [Authorisation | Laravel Actions](#authorisation-laravel-actions) - [Actions as controllers | Laravel Actions](#actions-as-controllers-laravel-actions) - [Actions as jobs | Laravel Actions](#actions-as-jobs-laravel-actions) - [Actions as listeners | Laravel Actions](#actions-as-listeners-laravel-actions) - [Actions as commands | Laravel Actions](#actions-as-commands-laravel-actions) - [Laravel Actions](#laravel-actions) - [Registering actions | Laravel Actions](#registering-actions-laravel-actions) - [Keeping track of how an action was run | Laravel Actions](#keeping-track-of-how-an-action-was-run-laravel-actions) - [Laravel Actions](#laravel-actions) - [Actions within actions | Laravel Actions](#actions-within-actions-laravel-actions) - [The lifecycle of an action | Laravel Actions](#the-lifecycle-of-an-action-laravel-actions) - [Laravel Actions](#laravel-actions) - [Laravel Actions](#laravel-actions) - [Laravel Actions](#laravel-actions) - [Laravel Actions](#laravel-actions) - [Laravel Actions](#laravel-actions) --- # Laravel Actions [#](#laravel-actions) Laravel Actions ====================================== ⚡ **Classes that take care of one specific task.** This package introduces a new way of organising the logic of your Laravel applications by focusing on the actions your applications provide. Instead of creating controllers, jobs, listeners and so on, it allows you to create a PHP class that handles a specific task and run that class as anything you want. Therefore it encourages you to switch your focus from: > "What controllers do I need?", "should I make a FormRequest for this?", "should this run asynchronously in a job instead?", etc. to: > "What does my application actually do?" ![Hero](/hero2.png) [Installation](/2.x/installation.html) → --- # Installation | Laravel Actions [#](#installation) Installation ================================ [#](#install-the-package) Install the package ---------------------------------------------- All you need to do to get started is add Laravel Action to your composer dependencies. composer require lorisleiva/laravel-actions You can then add the `AsAction` trait to any of your classes to make it an action. use Lorisleiva\Actions\Concerns\AsAction; class UpdateUserPassword { use AsAction; public function handle(User $user, string $newPassword) { // ... } } [#](#install-ide-helper-for-autocomplete-optional) Install ide helper for autocomplete (optional) -------------------------------------------------------------------------------------------------- If you use a PHP version of `^8.0` you may want to install an additional package similar to [laravel-ide-helper (opens new window)](https://github.com/barryvdh/laravel-ide-helper) which enables your IDE to provide accurate autocompletion for your actions. All you need to do is add `wulfheart/laravel-actions-ide-helper` to your dependencies. composer require --dev wulfheart/laravel-actions-ide-helper You can now generate the docs for yourself by running the command below. This will generate the file `_ide_helper_actions.php` which is expected to be additionally parsed by your IDE for autocomplete. php artisan ide-helper:actions [#](#publish-the-action-stub-optional) Publish the action stub (optional) -------------------------------------------------------------------------- You may publish the stub used by the `make:action` command if you want to modify it. php artisan vendor:publish --tag=stubs --provider="Lorisleiva\Actions\ActionServiceProvider" ← [Introduction](/) [Basic usage](/2.x/basic-usage.html) → --- # Laravel Actions [#](#laravel-actions) Laravel Actions ====================================== ⚡Laravel components that take care of one specific task This package introduces a new way of organising the logic of your Laravel applications by focusing on the actions your application provides. Similarly to how VueJS components regroup HTML, JavaScript and CSS together, Laravel Actions regroup the authorisation, validation and execution of a task in one class that can be used as an **invokable controller**, as a **plain object**, as a **dispatchable job**, as an **event listener** and as an **artisan command**. ![Hero](/hero.png) [Installation](/1.x/installation.html) → --- # Basic usage | Laravel Actions [#](#basic-usage) Basic usage ============================== First, start by creating a simple PHP class that handles your task. For the sake of this tutorial, let's create a simple class that updates a user's password. You can organise these actions however you want. Personally, I like to place these under an `app/Actions` folder or — if my app is separated into modules — under `app/MyModule/Actions`. namespace App\Authentication\Actions; class UpdateUserPassword { public function handle(User $user, string $newPassword) { $user->password = Hash::make($newPassword); $user->save(); } } Next, add the `AsAction` trait to your class. This will enable you to use this class as **an object**, **a controller**, **a job**, **a listener**, **a command** and even as **a fake** instance for testing and mocking purposes. namespace App\Authentication\Actions; use Lorisleiva\Actions\Concerns\AsAction; class UpdateUserPassword { use AsAction; public function handle(User $user, string $newPassword) { $user->password = Hash::make($newPassword); $user->save(); } } [#](#running-as-an-object) Running as an object ------------------------------------------------ The `AsAction` trait provides a couple of methods that help you resolve the class from the container and execute it. // Equivalent to "app(UpdateUserPassword::class)". UpdateUserPassword::make(); // Equivalent to "UpdateUserPassword::make()->handle($user, 'secret')". UpdateUserPassword::run($user, 'secret'); However, these are just here as additional helper methods. Feel free to instantiate and run your actions however you prefer. For example, you can resolve them from the container using dependency injection. class MySecurityService { protected UpdateUserPassword $updatePassword; public function __construct(UpdateUserPassword $updatePassword) { $this->updatePassword = $updatePassword; } } [#](#running-as-a-controller) Running as a controller ------------------------------------------------------ Now, let's use our action as a controller. First, we need to register it in our routes file just like we would register any invokable controller. Route::put('auth/password', UpdateUserPassword::class)->middleware('auth'); Then, all we need to do is implement the `asController` method so we can translate the request data into the arguments our action expect — in this case, a user object and a password. class UpdateUserPassword { use AsAction; public function handle(User $user, string $newPassword) { $user->password = Hash::make($newPassword); $user->save(); } public function asController(Request $request) { $this->handle( $request->user(), $request->get('password') ); return redirect()->back(); } } And just like that, you're using your custom PHP class as a controller. But what about authorization and validation? Shouldn't we make sure the new password was confirmed and the old password provided? Sure, let's do that. [#](#adding-controller-validation) Adding controller validation ---------------------------------------------------------------- Instead of injecting the regular `Request` class, we can either inject a custom `FormRequest` class or inject the `ActionRequest` class which will use the action itself to resolve authorization and validation. use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\ActionRequest; use Illuminate\Validation\Validator; class UpdateUserPassword { use AsAction; // ... public function rules() { return [\ 'current_password' => ['required'],\ 'password' => ['required', 'confirmed'],\ ]; } public function withValidator(Validator $validator, ActionRequest $request) { $validator->after(function (Validator $validator) use ($request) { if (! Hash::check($request->get('current_password'), $request->user()->password)) { $validator->errors()->add('current_password', 'The current password does not match.'); } }); } public function asController(ActionRequest $request) { $this->handle( $request->user(), $request->get('password') ); return redirect()->back(); } } And that's it! Now, when we reach the `asController` method, we know for sure the validation was successful and we can access the validated data using `$request->validated()` like we're used to. [#](#running-as-a-command) Running as a command ------------------------------------------------ Before wrapping up this tutorial, let's see how we could run our action as a command. Similarly to what we did earlier, we simply need to implement the `asCommand` method to transform our command-line arguments and options into a user object and a password. This method accepts a `Command` as an argument that can be used to both read input and write output. Additionally, we need to provide the command signature and description via the `$commandSignature` and `$commandDescription` properties. class UpdateUserPassword { use AsAction; public string $commandSignature = 'user:update-password {user_id} {password}'; public string $commandDescription = 'Updates the password a user.'; public function asCommand(Command $command) { $user = User::findOrFail($command->argument('user_id')); $this->handle($user, $command->argument('password')); $command->line(sprintf('Password updated for %s.', $user->name)); } // ... } Now we can register it in our console `Kernel` like so: namespace App\Console; class Kernel extends ConsoleKernel { protected $commands = [\ UpdateUserPassword::class,\ ]; // ... } [#](#next-steps) Next steps ---------------------------- Hopefully, this little tutorial helped to see what this package can achieve for you. On top of controllers and commands, Laravel Actions also supports jobs and listeners following the same conventions — by implementing the `asJob` and `asListener` methods. Better yet, **your custom PHP class is never directly used as a controller, job, command or listener**. Instead, it is wrapped in an appropriate decorator based on what it is running as. This means you have full control of your actions and you don't need to worry about cross-pattern conflicts (See "[How does it work?](./how-does-it-work) "). If you like learning by reading code, the "[Learn with examples](./examples/generate-reservation-code) " section is for you. Each example provides the code of one action, how it's being used or registered and a brief description explaining its purpose. Be sure to also check the "[Guide](./one-class-one-task) " and "[References](./as-object) " sections to gain more knowledge on what you can do with actions and to refer back to methods made available to you. ← [Installation](/2.x/installation.html) [Upgrade from 1.x](/2.x/upgrade.html) → --- # Generate reservation code | Laravel Actions [#](#generate-reservation-code) Generate reservation code ========================================================== [#](#definition) Definition ---------------------------- This action generates a unique reservation code using a non-ambiguous alphabet. class GenerateReservationCode { use AsAction; const UNAMBIGUOUS_ALPHABET = 'BCDFGHJLMNPRSTVWXYZ2456789'; public function handle(int $characters = 7): string { do { $code = $this->generateCode($characters); } while(Reservation::where('code', $code)->exists()); return $code; } protected function generateCode(int $characters): string { return substr(str_shuffle(str_repeat(static::UNAMBIGUOUS_ALPHABET, $characters)), 0, $characters); } } [#](#using-as-an-object) Using as an object -------------------------------------------- In a real-life application this action would typically be nested inside another action that create a new reservations. class CreateNewReservation { use AsAction; public function handle(User $user, Concert $concert, int $tickets = 1): Reservation { return $user->reservations()->create([\ 'concert_id' => $concert->id,\ 'price' => $concert->getTicketPrice() * $tickets,\ 'code' => GenerateReservationCode::run(),\ ]); } } [#](#using-as-a-fake-instance) Using as a fake instance -------------------------------------------------------- The advantage of using `::make()` or `::run()` is that it will resolve the action from the container. That means we can then easily swap its implementation for a mock when testing. /** @test */ public function it_generates_a_unique_code_when_creating_a_new_reservation() { // Given an existing user and concert. $user = User::factory()->create(); $concert = Concert::factory()->create(); // And given we mock the code generator. GenerateReservationCode::shouldRun()->andReturn('ABCD234'); // When we create a new reservation for that user and that concert. $reservation = CreateNewReservation::run($user, $concert); // Then we saved the expected reservation code. $this->assertSame('ABCD234', $reservation->code); } ← [Upgrade from 1.x](/2.x/upgrade.html) [Get user profile](/2.x/examples/get-user-profile.html) → --- # Get user profile | Laravel Actions [#](#get-user-profile) Get user profile ======================================== [#](#definition) Definition ---------------------------- A simple action used to retrieve a user's profile either via HTML or JSON. class GetUserProfile { use AsAction; public function asController(User $user, Request $request): User { if ($request->expectsJson()) { return new UserProfileResource($user); } return view('users.show', compact('user')); } } Since we're only planning on using this action as a controller, we can use the `handle` method directly and the arguments will be resolved just like an invokable controller. Additionally, we can use the helper method `htmlResponse` and `jsonResponse` to avoid that common if statement. The action below is equivalent to the one above. class GetUserProfile { use AsAction; public function handle(User $user): User { return $user; } public function htmlResponse(User $user) { return view('users.show', compact('user')); } public function jsonResponse(User $user) { return new UserProfileResource($user); } } [#](#registering-the-controller) Registering the controller ------------------------------------------------------------ To use as a controller simply register the action in your routes file. Route::get('users/{user}', GetUserProfile::class); [#](#adding-middleware) Adding middleware ------------------------------------------ Instead of defining the middleware where you define the route, you can add them directly in the action like so: class GetUserProfile { use AsAction; public function getControllerMiddleware(): array { return ['auth', MyCustomMiddleware::class]; } // ... } ← [Generate reservation code](/2.x/examples/generate-reservation-code.html) [Create new article](/2.x/examples/create-new-article.html) → --- # Upgrade from 1.x | Laravel Actions [#](#upgrade-from-1-x) Upgrade from 1.x ======================================== Laravel Actions v2 has been re-written from scratch and provides a slightly different paradigm than v1. Therefore, in all honesty, upgrading to v2 is not going to be trivial. [#](#a-different-paradigm) A different paradigm ------------------------------------------------ The main difference is that v1 uses an array of attributes — similarly to how Eloquent models work — in order to unify data between patterns (i.e. controllers, jobs, listeners, etc.). Additionally, the action itself becomes the pattern. So if you're running an action as a controller and a job, the action needs to act as both a controller and a job. That requires the action to extends an `Action` class that acts as a hybrid of all supported patterns and override some core Laravel components to make that work. // v1 class UpdateUserPassword extends Action { public function handle(): void { $this->user()->update([\ 'password' => Hash::make($this->password),\ ]); } } On the other hand, v2 no longer forces your actions to extend anything and gives you the freedom to write your action classes exactly as you want them to be. Instead, it uses traits to provide helper methods and recognise that your action wants to be acted on in a certain way. Each pattern comes with their own trait — `AsController`, `AsJob`, etc. — and are bundled together in a `AsAction` trait (See "[More granular traits](./granular-traits) "). Additionally, your action is no longer directly used as the pattern. Instead, it is wrapped in a decorator that will delegate to your actions when it needs to (See "[How does it work?](./how-does-it-work) "). // v2 class UpdateUserPassword { use AsAction; public function handle(User $user, string $newPassword): void { $user->update([\ 'password' => $newPassword,\ ]); } } The bottom line here is: a refactoring from v1 to v2 might be a good opportunity to rethink some of your actions now that you're free to implement them without any constraints. Whilst it is impossible to provide a step-by-step guide to upgrading to v2, the next sections focus on providing snippets of before/after code to help you see what has changed. [#](#no-more-attributes) No more attributes -------------------------------------------- By default, there is no longer an array of attributes in your actions. Thus, you might want to rethink how your action should organise its data. // v1 class CreateNewArticle extends Action { public function handle(): Article { return $this->user()->articles()->create([\ 'title' => $this->title,\ 'body' => $this->body,\ ]); } } // v2 class CreateNewArticle { use AsAction; public function handle(User $author, string $title, string $body): Article { return $author->articles()->create([\ 'title' => $title,\ 'body' => $body,\ ]); } } However, since version `2.1`, there is an optional `WithAttributes` trait that you can add to your action to keep the array of unified attributes in your actions. Just like in Laravel Actions v1, your actions now have access to methods like `set`, `get` or `fill` to handle attributes and attributes can be retrieved and updated like properties. Contrary to Laravel Actions v1 though, you will need to explicitly fill the attributes since methods like `getAttributesFromConstructor` no longer exist. This can be done in many ways. Your `handle` method could accept an array of attributes as arguments or it could instead expect attributes to have been filled beforehand. Or it could do a mixture of both. Here's an example using the `CreateNewArticle` action from above. // v2 with attributes. class CreateNewArticle { use AsAction; use WithAttributes; public function handle(User $author, array $attributes = []): Article { $this->set('author', $author); $this->fill($attributes); return $this->createArticle(); } protected function createArticle(): Article { return $this->author->articles()->create( $this->only('title', 'body') ); } } As you can see, once we fill in the attributes, they are accessible from anywhere within the action. Read "[Use unified attributes](./use-unified-attributes) " for more information. [#](#authorization-and-validation-for-controllers-only) Authorization and validation for controllers only ---------------------------------------------------------------------------------------------------------- Because we no longer have attributes to unify data between patterns, authorization and validation will only affect the action when it is running as a controller — and therefore when a request is available. Since version `2.1`, if you're using the `WithAttributes` trait, you can trigger authorization and validation using the action's attributes via the `validateAttributes` method. Read "[Use unified attributes](./use-unified-attributes) " for more information. [#](#inject-dependencies-in-the-constructor) Inject dependencies in the constructor ------------------------------------------------------------------------------------ Since your actions will always be resolved from the container, you may now use the `__construct` method to inject some dependencies into your action. // v2 class GetDirectionsToRestaurant { use AsAction; protected GoogleMapsService $googleMaps; public function __construct(GoogleMapsService $googleMaps) { $this->googleMaps = $googleMaps; } } [#](#one-method-for-both-input-and-output) One method for both input and output -------------------------------------------------------------------------------- On v1, you could use the `asX` methods to insert logic _before_ the `handle` method is executed and/or the `getAttributesFromX` methods to provide custom parsing between the pattern and the attributes. You'd then need to implement another method such as `response` or `consoleOutput` to insert logic _after_ the `handle` method. On v2, you no longer need to remember which method to call for hooking data before and/or after for each pattern. Instead, you have exactly one point of entry for each pattern: `asController`, `asCommand`, etc. These methods are now responsible for both the input and the output of that pattern since you'll be calling the `handle` method directly in there. // v1 class CreateNewArticle extends Action { public function getAttributesFromCommand(Command $command): array { $this->actingAs(User::findOrFail($command->argument('user_id'))); return [\ 'title' => $command->argument('title'),\ 'body' => $command->argument('body'),\ ]; } public function handle(): Article { return $this->user()->articles()->create([\ 'title' => $this->title,\ 'body' => $this->body,\ ]); } public function consoleOutput($article, Command $command): void { $command->info("Article \"{$article->title}\" created."); } } // v2 class CreateNewArticle { use AsAction; public function handle(User $author, string $title, string $body): Article { return $author->articles()->create([\ 'title' => $title,\ 'body' => $body,\ ]); } public function asCommand(Command $command): Article { $article = $this->handle( User::findOrFail($command->argument('user_id')), $command->argument('title')), $command->argument('body')), ); $command->info("Article \"{$article->title}\" created."); } } [#](#queue-fake-and-job-decorators) Queue fake and job decorators ------------------------------------------------------------------ If you're using `Queue::fake()` in your tests to assert an action was dispatched as a job, these tests will now fail due to the fact that the job is now a `JobDecorator` wrapping your action and not the action itself. To fix this, you simply need to replace `Queue::assertPushed(MyAction::class)` to `MyAction::assertPushed()`. See ["Asserting jobs were pushed"](/2.x/dispatch-jobs.html#asserting-jobs-were-pushed) for more information. [#](#methods-and-properties-map) Methods and properties map ------------------------------------------------------------ The table below provides a mapping between the methods and properties available in v1 and the ones available in v2 ordered alphabetically. | v1 | v2 | Comments | | --- | --- | --- | | `actingAs` | _Removed_ | No more user helpers. If an action requires a user to work, simply pass the user as an argument. | | `afterValidator` | `afterValidator` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `all` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | `asCommand` | `asCommand` | Same name but different behaviour. `asCommand` is now the single point of entry when the action is executed as a command (See "[One method for both input and output](#one-method-for-both-input-and-output)
"). | | `asController` | `asController` | Same name but different behaviour. `asController` is now the single point of entry when the action is executed as a controller (See "[One method for both input and output](#one-method-for-both-input-and-output)
"). | | `asJob` | `asJob` | Same name but different behaviour. `asJob` is now the single point of entry when the action is dispatched as a job (See "[One method for both input and output](#one-method-for-both-input-and-output)
"). | | `asListener` | `asListener` | Same name but different behaviour. `asListener` is now the single point of entry when the action is executed as a listener (See "[One method for both input and output](#one-method-for-both-input-and-output)
"). | | `attributes` | `getValidationAttributes` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `authorize` | `authorize` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `can` | _Removed_ | On v2, there's no `$user` property anymore. | | `$commandSignature` | `$commandSignature` | Same but need to be `public`. On v2, you can also use the `getCommandSignature` method instead. | | `$commandDescription` | `$commandDescription` | Same but need to be `public`. On v2, you can also use the `getCommandDescription` method instead. | | `consoleOutput` | _Removed_ | You can now provide the command's output directly in the `asCommand` method. | | `delegateTo` | _Removed_ | Just use `MyOtherAction::run` instead. The same goes for `createFrom` and `runAs`. | | `$errorBag` | `getValidationErrorBag` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `except` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | `failedAuthorization` | `getAuthorizationFailure` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `failedValidation` | `getValidationFailure` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `fill` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | _Added_ | `fillFromRequest` | From the optional `WithAttributes` trait. Fills attributes using the request data and its route parameters. | | `get` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | `getAttributesFromCommand` | _Removed_ | You can now parse the command's input directly in the `asCommand` method. | | `getAttributesFromConstructor` | _Removed_ | Actions no longer have attributes. When using the `WithAttributes` trait, you need to explicitly set your attributes. | | `getAttributesFromEvent` | _Removed_ | You can now parse the event data directly in the `asListener` method. | | `getAttributesFromRequest` | _Removed_ | You can now parse the request data directly in the `asController` method. | | `getRedirectUrl` | `getValidationRedirect` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `handle` | `handle` | Same method but it no longer resolves the attributes from its arguments since v2 no longer has attributes. Instead, you have full control over your method signature. | | `has` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | `initialized` | _Removed_ | You can now use `__construct` instead. | | _Added_ | `make` | _(Static)_ Equivalent to `app(MyAction::class)`. | | `messages` | `getValidationMessages` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `middleware` | `getControllerMiddleware` or `getJobMiddleware` | On v2, you have to explicitely provide middleware for controllers and/or jobs. On v1, there could have been conflicts between the two. | | `only` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | `prepareForValidation` | `prepareForValidation` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `registered` | _Removed_ | On v2, actions are recognised on-demand instead of being registered in a service provider. | | `response` | _Removed_ | You can now provide the controller's response directly in the `asController` method. Note that `htmlResponse` and `jsonResponse` still exist. | | `routes` | `routes` | _(static)_ Same method but you need to locate your actions in a service provider for this to work (See "[Registering routes directly in the action](/2.x/register-as-controller.html#registering-routes-directly-in-the-action)
"). | | `rules` | `rules` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `run` | `run` | _(Static)_ Same behaviour but now works only statically. You can use `$action->handle(...)` if you're looking for a non-static way to run your action. | | `runningAs` | _Removed_ | You pattern-specific logic now live in the `asX` methods. | | `set` | _Moved to_ `WithAttributes` | Actions no longer have attributes. You can add this method back by using the `WithAttributes` trait. | | `user` | _Removed_ | No more user helpers. If an action requires a user to work, simply pass the user as an argument. | | _Added_ | `validateAttributes` | From the optional `WithAttributes` trait. Triggers the authorization and validation logic using the action's attributes. | | `validationData` | `getValidationData` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `validator` | `getValidator` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | | `withValidator` | `withValidator` | Same behaviour but only applies when running as a controller or when using the `WithAttributes` trait. | ← [Basic usage](/2.x/basic-usage.html) [Generate reservation code](/2.x/examples/generate-reservation-code.html) → --- # Create new article | Laravel Actions [#](#create-new-article) Create new article ============================================ [#](#definition) Definition ---------------------------- Creates a new article for the given user with the given data. It uses the authenticated user when used as a controller and provides some custom authorization and validation. class CreateNewArticle { use AsAction; public function handle(User $author, array $data): Article { return $author->articles()->create($data); } public function getControllerMiddleware(): array { return ['auth']; } public function authorize(ActionRequest $request): bool { return in_array($request->user()->role, ['author', 'admin']); } public function rules(): array { return [\ 'title' => ['required', 'min:8'],\ 'body' => ['required', IsValidMarkdown::class],\ 'published' => ['required', 'boolean'],\ ]; } public function asController(ActionRequest $request): Article { $data = $request->only('title', 'body'); $data['published_at'] = $request->get('published') ? now() : null; return $this->handle($request->user(), $data); } } [#](#using-as-an-object) Using as an object -------------------------------------------- Note how we can use this action different based on how it's running. Internally, we might want to allow ourselves to define a custom publication date whereas we only allow a `published` boolean to the outside world. CreateNewArticle::run($author, [\ 'title' => 'My article',\ 'body' => '# My article',\ 'published_at' => now()->addWeek(),\ ]) It is also important to note that the authorization and validation logic will only be applied to the action when it is running as a controller. [#](#registering-as-a-controller) Registering as a controller -------------------------------------------------------------- To use as a controller simply register the action in your routes file. Route::post('articles', CreateNewArticle::class); ← [Get user profile](/2.x/examples/get-user-profile.html) [Export user data](/2.x/examples/export-user-data.html) → --- # Export user data | Laravel Actions [#](#export-user-data) Export user data ======================================== [#](#definition) Definition ---------------------------- Extracts all user data into a JSON file and send it to the user via email. class ExportUserData { use AsAction; public function handle(User $user): void { // Extract and store user data as a JSON file. $content = json_encode($this->getAllUserData($user)); $path = sprintf('user_exports/%s.json', $user->id); Storage::disk('s3')->replace($path, $content); // Send JSON file temporaty URL via email. Mail::to($user)->send(new UserDataExportReady($user, $path)); } protected function getAllUserData(User $user): array { return [\ 'profile' => $user->toArray(),\ 'articles' => $user->articles->toArray(),\ ] } } [#](#using-as-an-object) Using as an object -------------------------------------------- ExportUserData::run($user); [#](#using-as-an-asynchronous-job) Using as an asynchronous job ---------------------------------------------------------------- You can use the `dispatch` static method instead of `run` to dispatch the actions as an asynchronous job. Note that here we did not implement the `asJob` method since it would have been exactly the same as the `handle` method. In general, when no `asX` method is defined, the `handle` method is being used directly instead. ExportUserData::dispatch($user)->onQueue('my_queue'); If you prefer defining the queue — or any other job settings — directly in the action, you can do this using the `configureJob` method. use Lorisleiva\Actions\Decorators\JobDecorator; class ExportUserData { use AsAction; public function configureJob(JobDecorator $job): void { $job->onConnection('my_connection') ->onQueue('my_queue') ->through(['my_middleware']) ->chain(['my_chain']) ->delay(60); } // ... } [#](#using-as-a-synchronous-job) Using as a synchronous job ------------------------------------------------------------ You can use the `dispatchNow` method to dispatch the action as a synchronous job. Note that this is equivalent to using the action as an object using the `run` method. ExportUserData::dispatchNow($user); ← [Create new article](/2.x/examples/create-new-article.html) [Demote team membership](/2.x/examples/demote-team-membership.html) → --- # Demote team membership | Laravel Actions [#](#demote-team-membership) Demote team membership ==================================================== [#](#definition) Definition ---------------------------- Update the plan of a given team to `free` and disable projects that are no longer included in the plan. class DemoteTeamMembership { use AsAction; public string $commandSignature = 'teams:demote {team_id}'; public string $commandDescription = 'Demote the team with the given id.'; public function handle(Team $team): void { $team->update(['plan' => 'free' ]); $numberOfProjectsAllowed = config('app.plans.free.number_of_projects'); if ($team->projects()->count() <= $numberOfProjectsAllowed) { return; } $team->projects() ->orderBy('created_at') ->skip($numberOfProjectsAllowed) ->update(['disabled_at' => now()]); } public function asListener(PaymentFailed $event): void { $this->handle($event->payment->team); } public function asCommand(Command $command): void { $team = Team::findOrFail($command->argument('team_id')); $this->handle($team); $command->line('Done!'); } } [#](#using-as-an-object) Using as an object -------------------------------------------- DemoteTeamMembership::run($team); [#](#registering-as-a-listener) Registering as a listener ---------------------------------------------------------- To make your action listen to a particular event, simply add it to your `EventServiceProvider`. namespace App\Providers; class EventServiceProvider extends ServiceProvider { protected $listen = [\ PaymentFailed::class => [\ DemoteTeamMembership::class,\ ],\ ]; // ... } [#](#using-as-a-command) Using as a command -------------------------------------------- It could be useful to register the action as command should we need to manually demote a team. To do that we need to register our command in the console `Kernel`. namespace App\Console; class Kernel extends ConsoleKernel { protected $commands = [\ DemoteTeamMembership::class,\ ]; // ... } Now we can demote a team of id `42` like this: php artisan teams:demote 42 ← [Export user data](/2.x/examples/export-user-data.html) [Synchronize contacts from Google](/2.x/examples/synchronize-contacts-from-google.html) → --- # One class, one task | Laravel Actions [#](#one-class-one-task) One class, one task ============================================= Laravel Actions provide a new "unit of life" within your application: **An action**. This encourages you to focus on what your application actually does instead of the framework patterns it relies on. [#](#concretely-what-is-an-action) Concretely, what is an action? ------------------------------------------------------------------ An action can be any PHP class with a `handle` method. Just add the `AsAction` trait to that class and voilà, you've got yourself an action. It has only one constraint: **it must be able to resolve from the container** — meaning `app(MyAction::class)` should not fail. This means you can use the constructor to inject dependencies into your actions. use Lorisleiva\Actions\Concerns\AsAction; class MyFirstAction { use AsAction; protected MyInjectedService $service; public function __construct(MyInjectedService $service) { $this->service = $service; } public function handle(...$someArguments) { // Your action logic here... } } Note that Laravel Actions uses a trait instead of inheritance to be as unintrusive as possible. If you prefer inheritance, you can use the equivalent `extends \Lorisleiva\Actions\Action`. If you don't prefer inheritance, you might be interested in "[More granular traits](/2.x/granular-traits.html) ". [#](#running-as-an-object) Running as an object ------------------------------------------------ Because you have complete control over your action classes, you don't really need Laravel Actions to run it as an object. Feel free to instantiate and run your actions however you prefer. For example, you can resolve them from the container using dependency injection. However, Laravel Actions provides two helper static methods for you: `make` and `run`. These make it easier for you to **instantiate** and **execute** your action respectively. // Equivalent to "app(MyFirstAction::class)". MyFirstAction::make(); // Equivalent to "MyFirstAction::make()->handle($myArguments)". MyFirstAction::run($myArguments); No matter how you decide to instantiate your action, It is good practice to ensure it is always resolved from the container. That way: * You can always use dependency injection on its constructor. * You can replace the action with a mock on your tests (See "[Mock and test your actions](/2.x/mock-and-test.html) "). [#](#recommended-conventions) Recommended conventions ------------------------------------------------------ Even though you have full control over how to implement your actions, a few minor conventions can help you stay consistent when organising your application. Here are two recommended ones. ### [#](#start-with-a-verb) Start with a verb Name your action classes as **small explicit sentences that start with a verb**. For example, an action that "sends an email to the user to reset its password" could be named `SendResetPasswordEmail`. That way, your folder structure almost becomes an exhaustive dictionary of everything your application provides. This brings us to the second recommended convention. ### [#](#use-an-actions-folder) Use an `Actions` folder Create an `app/Actions` folder and group your actions inside this folder by topic. Here's a simple example. app/ ├── Actions/ │ ├── Authentication/ │ │ ├── LoginUser.php │ │ ├── RegisterUser.php │ │ ├── ResetUserPassword.php │ │ └── SendResetPasswordEmail.php │ ├── Leads/ │ │ ├── BulkRemoveLead.php │ │ ├── CreateNewLead.php │ │ ├── GetLeadDetails.php │ │ ├── MarkLeadAsCustomer.php │ │ ├── MarkLeadAsLost.php │ │ ├── RemoveLead.php │ │ ├── SearchLeadsForUser.php │ │ └── UpdateLeadDetails.php │ └── Settings/ │ ├── GetUserSettings.php │ ├── UpdateUserAvatar.php │ ├── UpdateUserDetails.php │ ├── UpdateUserPassword.php │ └── DeleteUserAccount.php ├── Models/ └── ... Alternatively, if your application is already divided into topics — or modules — you can create an `Actions` folder under each of these modules. For example: app/ ├── Authentication/ │ ├── Actions/ │ ├── Models/ │ └── ... ├── Leads/ │ ├── Actions/ │ ├── Models/ │ └── ... └── Settings/ ├── Actions/ └── ... [#](#how-does-it-work) How does it work? ----------------------------------------- So far, we've only seen how to run actions as objects, but you might be wondering how your classes are going to be executed as controllers, jobs, etc. Laravel Actions does that by adding a special interceptor on the container that recognise how the class is being run. When it does — and that's the important part — **it wraps your PHP class inside a decorator that will delegate to your action when it needs to**. Each design pattern has its own decorator — e.g. `ControllerDecorator`, `JobDecorator` and so on. That means you still have full control over your PHP class and need not worry about conflicts between various design patterns. Check out the "[How does it work?](/2.x/how-does-it-work.html) " page if you're interested in learning more about this. Now, let's move on to [controllers](/2.x/register-as-controller.html) . ← [Synchronize contacts from Google](/2.x/examples/synchronize-contacts-from-google.html) [Register your task as a controller](/2.x/register-as-controller.html) → --- # Synchronize contacts from Google | Laravel Actions [#](#synchronize-contacts-from-google) Synchronize contacts from Google ======================================================================== [#](#definition) Definition ---------------------------- Fetches all contacts from the user's Google account and synchronize it with our own definition of contacts. Concretely, it adds or updates the fetched contacts and deletes the ones that are no longer part of the fetched contacts. class SynchronizeContactsFromGoogle { use AsAction; protected Collection $fetchedIds; public string $commandSignature = 'users:sync-contacts {user_id}'; public string $commandDescription = 'Synchronize the Google contacts of the given user.'; public function __construct(): void { $this->fetchedIds = collect(); } public function handle(User $user): void { // Delegate to another action to fetch the actual data (makes it easier to mock). $googleContacts = FetchContactsFromGoogle::run($user); // Update or create contacts from the fetched data. $googleContacts->each( fn ($googleContact) => $this->upsertContact($user, $googleContact) ); // Remove any existing contacts that were not part of the fetched contacts. $user->contacts() ->whereNotIn('google_id', $this->fetchedIds) ->delete(); } protected function upsertContact(User $user, array $googleContact): void { $user->contacts()->updateOrCreate( [\ 'google_id' => Arr::get($googleContact, 'id')\ ], [\ 'name' => Arr::get($googleContact, 'name'),\ 'company' => Arr::get($googleContact, 'company'),\ 'phone' => Arr::get($googleContact, 'phone'),\ 'email' => Arr::get($googleContact, 'email'),\ ], ); $this->fetchedIds->push(Arr::get($googleContact, 'id')); } public function getControllerMiddleware(): array { return ['auth']; } public function asController(Request $request) { $this->handle($user = $request->user()); return ContactResource::collection($user->contacts); } public function asListener(GoogleAccountChanged $event): void { $this->handle($event->googleAccount->user); } public function asCommand(Command $command): void { $this->handle( User::findOrFail($command->argument('user_id')) ); $command->line('Done!'); } } [#](#using-as-an-object) Using as an object -------------------------------------------- SynchronizeContactsFromGoogle::run($user); [#](#registering-as-a-controller) Registering as a controller -------------------------------------------------------------- Route::post('users/contacts/sync', SynchronizeContactsFromGoogle::class); [#](#dispatching-as-an-asynchronous-job) Dispatching as an asynchronous job ---------------------------------------------------------------------------- SynchronizeContactsFromGoogle::dispatch($user); [#](#registering-as-a-listener) Registering as a listener ---------------------------------------------------------- namespace App\Providers; class EventServiceProvider extends ServiceProvider { protected $listen = [\ GoogleAccountChanged::class => [\ SynchronizeContactsFromGoogle::class,\ ],\ ]; // ... } [#](#registering-as-a-command) Registering as a command -------------------------------------------------------- namespace App\Console; class Kernel extends ConsoleKernel { protected $commands = [\ SynchronizeContactsFromGoogle::class,\ ]; // ... } ← [Demote team membership](/2.x/examples/demote-team-membership.html) [One class, one task](/2.x/one-class-one-task.html) → --- # Register your task as a controller | Laravel Actions [#](#register-your-task-as-a-controller) Register your task as a controller ============================================================================ [#](#registering-the-route) Registering the route -------------------------------------------------- To run your action as a controller, you simply need to register it in your routes file just like any other invokable controller. Route::post('/users/{user}/articles', CreateNewArticle::class); [#](#from-controller-to-action) From controller to action ---------------------------------------------------------- Because you have full control on how your actions are implemented, you need to translate the received request into a call to your `handle` method. You can use the `asController` method to define that logic. Its parameters will be resolved using route model binding just like it would in a controller. class CreateNewArticle { use AsAction; public function handle(User $user, string $title, string $body): Article { return $user->articles()->create(compact('title', 'body')); } public function asController(User $user, Request $request): Response { $article = $this->handle( $user, $request->get('title'), $request->get('body') ); return redirect()->route('articles.show', [$article]); } } If you're only planning on using your action as a controller, you can omit the `asController` method and use the `handle` method directly as an invokable controller. class CreateNewArticle { use AsAction; public function handle(User $user, Request $request): Response { $article = $user->articles()->create( $request->only('title', 'body') ) return redirect()->route('articles.show', [$article]); } } Note that, in this example, you loose the ability to run `CreateNewArticle::run($user, 'My title', 'My content')`. [#](#assigning-controller-middleware) Assigning controller middleware ---------------------------------------------------------------------- Instead of — or in addition to — defining your middleware in your routes file, you may also define them directly in the action using the `getControllerMiddleware` method. class CreateNewArticle { use AsAction; public function getControllerMiddleware(): array { return ['auth', MyCustomMiddleware::class]; } // ... } [#](#providing-a-different-response-for-json-and-html) Providing a different response for JSON and HTML -------------------------------------------------------------------------------------------------------- Oftentimes, you'll need your controllers — and therefore actions — to be available both as a web page and as a JSON API endpoint. You'll likely endup doing something like this a little bit everywhere. if ($request->expectsJson()) { return new ArticleResource($article); } else { return redirect()->route('articles.show', [$article]); } That's why Laravel Actions recognises two helper methods `jsonResponse` and `htmlResponse` that you can use to separate the response based on the request expecting JSON or not. These methods receive as a first argument the return value of the `asController` method and, as a second argument, the `Request` object. class CreateNewArticle { use AsAction; public function handle(User $user, string $title, string $body): Article { return $user->articles()->create(compact('title', 'body')); } public function asController(User $user, Request $request): Article { return $this->handle($user, $request->get('title'), $request->get('body')); } public function htmlResponse(Article $article): Response { return redirect()->route('articles.show', [$article]); } public function jsonResponse(Article $article): ArticleResource { return new ArticleResource($article); } } [#](#registering-routes-directly-in-the-action) Registering routes directly in the action ------------------------------------------------------------------------------------------ Now this is not for everybody but if you really want to take this "unit of life" to the next level, you may define your routes directly in the action by using the `routes` static method. It provides the `Router` as a first argument. class GetArticlesFromAuthor { use AsAction; public static function routes(Router $router) { $router->get('author/{author}/articles', static::class); } public function handle(User $author) { return $author->articles; } } However, in order for this to work, you need to tell Laravel Actions where your actions are located so it can loop through your static `routes` methods. For that all you need to do is call the `registerRoutes` method of the `Actions` Facade on a service provider. It will look recursively into the folders provided. use Lorisleiva\Actions\Facades\Actions; // Register routes from actions in "app/Actions" (default). Actions::registerRoutes(); // Register routes from actions in "app/MyCustomActionsFolder". Actions::registerRoutes('app/MyCustomActionsFolder'); // Register routes from actions in multiple folders. Actions::registerRoutes([\ 'app/Authentication',\ 'app/Billing',\ 'app/TeamManagement',\ ]); [#](#routes-with-explicit-methods) Routes with explicit methods ---------------------------------------------------------------- On some occasions, you might want to use the same action as more than one endpoint. In these situations, you may provide an explicit method when registering the route and that method will be used instead of the `asController` or `handle` method. This can be particularly helpful when you need to show a form that will then trigger the action. Route::get('/users/{user}/articles/create', [CreateNewArticle::class, 'showForm']); Route::post('/users/{user}/articles', CreateNewArticle::class); Note that, when providing an explicit method, no authorization (e.g. `authorize()`) or validation (e.g. `rules()`) will be automatically triggered on that endpoint. Also note that creating small dedicated actions for showing forms — such as `ShowNewArticleForm` — is perfectly fine and might even be a better approach depending on how you want to organise your application. Route::get('/users/{user}/articles/create', ShowNewArticleForm::class); Route::post('/users/{user}/articles', CreateNewArticle::class); Now that we're familiar on how to use actions as controllers, let's go one step further and see how Laravel Actions can handle [authorization and validation when being used as a controller](./add-validation-to-controllers) . ← [One class, one task](/2.x/one-class-one-task.html) [Add validation to your controllers](/2.x/add-validation-to-controllers.html) → --- # Add validation to your controllers | Laravel Actions [#](#add-validation-to-your-controllers) Add validation to your controllers ============================================================================ One way to add validation to your controllers is to inject a `FormRequest` in your `asController` method just as you would do in a controller. public function asController(MyFormRequest $request) { // Authorization and validation defined in MyFormRequest was successful. } However, that means yet another class, that is tightly coupled to this action, has to be created somewhere else in your application — typically in `app/Http/Requests`. This is why Laravel Actions provides a special request class called `ActionRequest`. An `ActionRequest` is a special `FormRequest` class that allows you to **define your authorization and validation directly within your action**. It will look for specific methods within your action and delegate to them when it needs to. use Lorisleiva\Actions\ActionRequest; public function asController(ActionRequest $request) { // Authorization and validation defined in this class was successful. } This page documents these special methods that you may implement to define your authorization and validation. [#](#authorization) Authorization ---------------------------------- Just like in a `FormRequest`, you may implement the `authorize` method that returns `true` if and only if the use is authorized to see access this action. public function authorize(ActionRequest $request): bool { return $request->user()->role === 'author'; } You may use the `can` method on your authenticated user or the `Gate` facade to check for specific abilities defined in Laravel. use Illuminate\Support\Facades\Gate; public function authorize(ActionRequest $request): bool { // Using the `can` method. return $request->user()->can('update', $request->route('article')); // Using the `Gate` facade (this allows for nullable users). return Gate::check('update', $request->route('article')); } Instead of returning a boolean, you may also return gate responses to provide a more detailed response. use Illuminate\Auth\Access\Response; public function authorize(ActionRequest $request): Response { if ($request->user()->role !== 'author') { return Response::deny('You must be an author to create a new article.'); } return Response::allow(); } Just like in a `FormRequest`, it will return an `AuthorizationException` if authorization fails. You may provide your own authorization failure logic by implementing the `getAuthorizationFailure` method. public function getAuthorizationFailure(): void { throw new MyCustomAuthorizationException(); } [#](#adding-validation-rules) Adding validation rules ------------------------------------------------------ You may implement the `rules` method to provide the rules to validate against the request data. public function rules(): array { return [\ 'title' => ['required', 'min:8'],\ 'body' => ['required', IsValidMarkdown::class],\ ]; } You may then use the `validated` method inside your `asController` method to access the request data that went through your validation rules. public function asController(ActionRequest $request) { $request->validated(); } [#](#custom-validation-logic) Custom validation logic ------------------------------------------------------ In addition to your validation `rules`, you may provide the `withValidator` method to provide custom validation logic. It works just like in a `FormRequest` and provides the validator as a first argument allowing you to add "after validation callbacks". use Illuminate\Validation\Validator; public function withValidator(Validator $validator, ActionRequest $request): void { $validator->after(function (Validator $validator) use ($request) { if (! Hash::check($request->get('current_password'), $request->user()->password)) { $validator->errors()->add('current_password', 'Wrong password.'); } }); } Very often, when you use `withValidator`, you just want to add a `after` callback on the validator. Laravel Actions conveniently allows you to implement the `afterValidator` method directly to avoid the nested callback. use Illuminate\Validation\Validator; public function afterValidator(Validator $validator, ActionRequest $request): void { if (! Hash::check($request->get('current_password'), $request->user()->password)) { $validator->errors()->add('current_password', 'Wrong password.'); } } Alternatively, if you want full control over the validator that will be generated, you may implement the `getValidator` method instead. Implementing this method will ignore any other validation methods such as `rules`, `withValidator` and `afterValidator`. use Illuminate\Validation\Factory; use Illuminate\Validation\Validator; public function getValidator(Factory $factory, ActionRequest $request): Validator { return $factory->make($request->only('title', 'body'), [\ 'title' => ['required', 'min:8'],\ 'body' => ['required', IsValidMarkdown::class],\ ]); } [#](#prepare-for-validation) Prepare for validation ---------------------------------------------------- Just like in a `FormRequest`, you may provide the `prepareForValidation` method to insert some custom logic before both authorization and validation are triggered. public function prepareForValidation(ActionRequest $request): void { $request->merge(['some' => 'additional data']); } [#](#custom-validation-messages) Custom validation messages ------------------------------------------------------------ You may also customise the messages of your validation rules and provide some human-friendly mapping to your request attributes by implementing the `getValidationMessages` and `getValidationAttributes` methods respectively. public function getValidationMessages(): array { return [\ 'title.required' => 'Looks like you forgot the title.',\ 'body.required' => 'Is that really all you have to say?',\ ]; } public function getValidationAttributes(): array { return [\ 'title' => 'headline',\ 'body' => 'content',\ ]; } Note that providing the `getValidator` method will ignore both of these methods too. [#](#custom-validation-failure) Custom validation failure ---------------------------------------------------------- Just like in a `FormRequest`, it will return an `ValidationException` if validation fails. This exception will, by default, redirect to the previous page and use the `default` error bag on the validator. You may customise both of these behaviours by implementing the `getValidationRedirect` and `getValidationErrorBag` methods respectively. use Illuminate\Routing\UrlGenerator; public function getValidationRedirect(UrlGenerator $url): string { return $url->to('/my-custom-redirect-url'); } public function getValidationErrorBag(): string { return 'my_custom_error_bag'; } Alternatively, you may override the validation failure that is being thrown altogether by implementing the `getValidationFailure` method. public function getValidationFailure(): void { throw new MyCustomValidationException(); } Okay enough about controllers, let's now see how we can [dispatch our actions as asynchronous jobs](./dispatch-jobs) . ← [Register your task as a controller](/2.x/register-as-controller.html) [Dispatch asynchronous jobs](/2.x/dispatch-jobs.html) → --- # Mock and test your actions | Laravel Actions [#](#mock-and-test-your-actions) Mock and test your actions ============================================================ One of the advantages of using Laravel Actions is that it ensures your actions are resolved from the container — even when executing them as simple objects. This means, we can easily leverage this to swap their implementation with a mock or a spy to make testing easier. [#](#mocking) Mocking ---------------------- To replace an action with a mock in your test, simply use the `mock` static method like so: FetchContactsFromGoogle::mock(); This will return a `MockInterface` and thus you can chain your mock expectations as you're used to. FetchContactsFromGoogle::mock() ->shouldReceive('handle') ->with(42) ->andReturn(['Loris', 'Will', 'Barney']); Since you'll likely be mocking the `handle` method the most, you may also use the helper method `shouldRun` to make it easier to read. The code below is equivalent to the previous example. FetchContactsFromGoogle::shouldRun() ->with(42) ->andReturn(['Loris', 'Will', 'Barney']); You may also use the helper method `shouldNotRun` to add the opposite expectation. FetchContactsFromGoogle::shouldNotRun(); // Equivalent to: FetchContactsFromGoogle::mock()->shouldNotReceive('handle'); [#](#partial-mocking) Partial mocking -------------------------------------- If you only want to mock the methods that have expectations, you may use the `partialMock` method instead. In the example below, only the `fetch` method will be mocked. FetchContactsFromGoogle::partialMock() ->shouldReceive('fetch') ->with('some_google_identifier') ->andReturn(['Loris', 'Will', 'Barney']); [#](#spying) Spying -------------------- If you prefer running first and asserting after, you may use a spy instead of a mock by using the `spy` method. $spy = FetchContactsFromGoogle::spy(); $spy->allows('handle')->andReturn(['Loris', 'Will', 'Barney']); // ... $spy->shouldHaveReceived('handle')->with(42); You may also use the helper method `allowToRun` to make it slightly more readable. The code below is equivalent to the previous example. FetchContactsFromGoogle::allowToRun() ->andReturn(['Loris', 'Will', 'Barney']); // ... FetchContactsFromGoogle::spy() ->shouldHaveReceived('handle')->with(42); [#](#handling-fake-instances) Handling fake instances ------------------------------------------------------ When using `mock`, `partialMock` or `spy` on an action, it will generate a new `MockInterface` once and then keep on using the same fake instance. This means, no matter how many times you call the `mock` method, it will always reference the same `MockInterface`, allowing to keep adding expectations in your tests. Laravel Actions provides two additional methods to help your handle fake instances. The first one is a simple `isFake` method telling you if the action is currently being mocked or not. FetchContactsFromGoogle::isFake(); // false FetchContactsFromGoogle::mock(); FetchContactsFromGoogle::isFake(); // true The second one, `clearFake`, allows you to dattach the `MockInterface` from the action so it can go back to its real implementation. FetchContactsFromGoogle::mock(); FetchContactsFromGoogle::isFake(); // true FetchContactsFromGoogle::clearFake(); FetchContactsFromGoogle::isFake(); // false And that's all there is to it. Congratulations, you've now finished the main part of this guide! 🎉 The next two pages are optional and slightly more advanced. The first one explains [how to use more granular traits](./granular-traits) than `AsAction` and the second one dig a bit deeper into [how Laravel Actions works under the hood](./how-does-it-work) . ← [Execute as commands](/2.x/execute-as-commands.html) [More granular traits](/2.x/granular-traits.html) → --- # More granular traits | Laravel Actions [#](#more-granular-traits) More granular traits ================================================ [#](#a-collection-of-traits) A collection of traits ---------------------------------------------------- If you look closer at the `AsAction` trait provided by Laravel Actions, you'll notice it's just a collection of smaller traits. namespace Lorisleiva\Actions\Concerns; trait AsAction { use AsObject; use AsController; use AsListener; use AsJob; use AsCommand; use AsFake; } Each of these traits: * **Provide** methods to the action and/or * Enable the decorator to **use** methods from the action. You can check the "[References](./as-object) " section of this documentation to see all methods provided and/or used by each of these traits. [#](#cherry-picking) Cherry-picking ------------------------------------ This means you can cherry-pick the part of Laravel Actions you want to use. For example, you could explicitely cherry-pick these traits for each action. class MyAction { use AsObject; use AsController; // ... } Or, you could create your own `AsAction` trait that only includes the features of Laravel Actions your application needs. use Lorisleiva\Actions\Concerns\AsObject; use Lorisleiva\Actions\Concerns\AsController; use Lorisleiva\Actions\Concerns\AsJob; use Lorisleiva\Actions\Concerns\AsFake; trait AsAction { use AsObject; use AsController; use AsJob; use AsFake; } The benefits of this cherry-picking are very small — which is why the documentation focuses on the `AsAction` trait — but here they are: * Remove potential conflicting methods. For example, if you want to define your own `rules` method on an action that will never run as a controller. * Tiny (negligeable) performance improvements since we'll be looping through less patterns to identify how an action is being executed. * That's it... Cherry-picking can be particularly useful if you only need one slice of Laravel Actions. For example, if all you need is a way to run your plain PHP classes as controllers, then all you need to use is the `AsController` trait. ← [Mock and test your actions](/2.x/mock-and-test.html) [How does it work?](/2.x/how-does-it-work.html) → --- # Dispatch asynchronous jobs | Laravel Actions [#](#dispatch-asynchronous-jobs) Dispatch asynchronous jobs ============================================================ [#](#from-job-to-action) From job to action -------------------------------------------- When it comes to dispatching your actions as jobs, implementing the `handle` method should typically be enough. The reason for that is you'll likely want to use the same arguments when running an action as an object (`MyAction::run`) and when dispatching it as a job (`MyAction::dispatch`). For example, say you have an action that sends a report email to every member of a team. class SendTeamReportEmail { use AsAction; public function handle(Team $team): void { // Prepare report and send it to all $team->users. } } Using this `handle` method, you'll be dispatch it as a job by running `SendTeamReportEmail::dispatch($someTeam)`. However, if the logic around dispatching a job differs from the `handle` method, then you may implement the `asJob` method. For example, we might want to send a full report only when dispatched as a job. class SendTeamReportEmail { use AsAction; public function handle(Team $team, bool $fullReport = false): void { // Prepare report and send it to all $team->users. } public function asJob(Team $team): void { $this->handle($team, true); } } [#](#dispatching-jobs) Dispatching jobs ---------------------------------------- ### [#](#asynchronously) Asynchronously Dispatching jobs asynchronously can be done using the `dispatch` method. SendTeamReportEmail::dispatch($team); Behind the scene, this will create a new `JobDecorator` and wrap your action inside it. This means you cannot dispatch a job using the `dispatch` helper method. // This will NOT work. ❌ dispatch(SendTeamReportEmail::make()); If you must use the `dispatch` helper method, then you'll need to use `makeJob` instead and pass it the action's arguments. // This will work. ✅ dispatch(SendTeamReportEmail::makeJob($team)); You may also use the `dispatchIf` and `dispatchUnless` method to dispatch a job under a certain condition. SendTeamReportEmail::dispatchIf($team->hasAddon('reports'), $team); SendTeamReportEmail::dispatchUnless($team->missesAddon('reports'), $team); ### [#](#synchronously) Synchronously Although you can use `SendTeamReportEmail::run($team)` to execute an action immediately, you may also dispatch a synchronous job using the `dispatchNow` or `dispatchSync` methods. SendTeamReportEmail::dispatchNow($team); SendTeamReportEmail::dispatchSync($team); ### [#](#after-the-response-was-sent) After the response was sent You may delay the execution of an action after the response was sent to the user by using the `dispatchAfterResponse` method. SendTeamReportEmail::dispatchAfterResponse($team); ### [#](#with-chain) With chain Finally, you may chain multiple jobs together by using the `withChain` method. Make sure to use the `makeJob` method to instantiate the chained jobs — otherwise your action will not be wrapped in a `JobDecorator`. $chain = [\ OptimizeTeamReport::makeJob($team),\ SendTeamReportEmail::makeJob($team),\ ]; CreateNewTeamReport::withChain($chain)->dispatch($team); Note that you can achieve the same result by using the `chain` method on the `Bus` Facade. use Illuminate\Support\Facades\Bus; Bus::chain([\ CreateNewTeamReport::makeJob($team),\ OptimizeTeamReport::makeJob($team),\ SendTeamReportEmail::makeJob($team),\ ])->dispatch(); [#](#configuring-jobs) Configuring jobs ---------------------------------------- When dispatching a job, you'll receive a `PendingDispatch` allowing you to chain any job configuration you need. SendTeamReportEmail::dispatch($team) ->onConnection('my_connection') ->onQueue('my_queue') ->through(['my_middleware']) ->chain(['my_chain']) ->delay(60); } If you want to configure these options in the action itself so they are used by default whenever you dispatch it, you may use the `configureJob` method. It will provide the `JobDecorator` as a first argument which you can use to chain the same job configurations as above. use Lorisleiva\Actions\Decorators\JobDecorator; public function configureJob(JobDecorator $job): void { $job->onConnection('my_connection') ->onQueue('my_queue') ->through(['my_middleware']) ->chain(['my_chain']) ->delay(60); } Additionally, you may use any of the properties below to further configure and/or adjust the retry-logic of your jobs. class SendTeamReportEmail { use AsAction; public string $jobConnection = 'my_connection'; public string $jobQueue = 'my_queue'; public int $jobTries = 10; public int $jobMaxExceptions = 3; public int $jobBackoff = 60 * 5; public int $jobTimeout = 60 * 30; public int $jobRetryUntil = 3600 * 2; // ... } Since you might want to define the `backoff` and the `retryUntil` dynamically, you may instead used the `getJobBackoff` and `getJobRetryUntil` methods respectively. class SendTeamReportEmail { use AsAction; public function getJobBackoff(): array { return [30, 60, 120]; } public function getJobRetryUntil(): DateTime { return now()->addMinutes(30); } // ... } Also note that you can use the `configureJob` method to set the `tries`, `maxExceptions` and/or `timeout` job properties. public function configureJob(JobDecorator $job): void { $job->setTries(10) ->setMaxExceptions(3) ->setTimeout(60 * 30); } [#](#registering-job-middleware) Registering job middleware ------------------------------------------------------------ You may also attach job middleware to your actions by returning them from the `getJobMiddleware` method. public function getJobMiddleware(): array { return [new RateLimited('reports')]; } [#](#batching-jobs) Batching jobs ---------------------------------- Note that job batching is also supported. Simply use the `makeJob` method to create many jobs inside a batch. $batch = Bus::batch([\ SendTeamReportEmail::makeJob($firstTeam),\ SendTeamReportEmail::makeJob($secondTeam),\ SendTeamReportEmail::makeJob($thirdTeam),\ ])->then(function (Batch $batch) { // All jobs completed successfully... })->catch(function (Batch $batch, Throwable $e) { // First batch job failure detected... })->finally(function (Batch $batch) { // The batch has finished executing... })->dispatch(); When dispatching jobs in batch, you might want to access the `$batch` instance from the `asJob` method. You may do this by prepending your arguments with `?Batch $batch`. Note that the `?` is important since the job might also be dispatched normally — i.e. not in a batch. Laravel Actions uses `Reflection` to only provide that argument when you request it. use Illuminate\Bus\Batch; public function asJob(?Batch $batch, Team $team) { if ($batch && $batch->cancelled()) { return; } $this->handle($team, true); } Note that you may also inject the `JobDecorator` instead of the `?Batch` if you need to. use Lorisleiva\Actions\Decorators\JobDecorator; public function asJob(JobDecorator $job, Team $team) { if ($job->batch() && $job->batch()->cancelled()) { return; } $this->handle($team, true); } [#](#unique-jobs) Unique jobs ------------------------------ The Laravel framework provides a `ShouldBeUnique` trait that you can use on a job to ensure it runs only once for a given identifier and for a given amount of time. With a traditional job, it looks like this. use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldBeUnique; class SendTeamReportEmail implements ShouldQueue, ShouldBeUnique { public Team $team; public int $uniqueFor = 3600; public function uniqueId() { return $this->team->id; } // ... } With Laravel Actions, you can still achieve this by adding the `ShouldBeUnique` trait to your action. * To define the unique identifier you may use the `$jobUniqueId` property or the `getJobUniqueId` method. * To define the amount of time in which a job should stay unique, you may use the `$jobUniqueFor` property or the `getJobUniqueFor` method. When you use either of these methods, their arguments will be the same as the job's arguments themselves. For instance, the example above can be rewriten as an action like so: use Illuminate\Contracts\Queue\ShouldBeUnique; class SendTeamReportEmail implements ShouldBeUnique { use AsAction; public int $jobUniqueFor = 3600; public function getJobUniqueId(Team $team) { return $team->id; } // ... } By default, the default cache driver will be used to obtain the lock and therefore maintain the unicity of the jobs being dispatched. You may specify which cache driver to use for a particular action by implementing the `getJobUniqueVia` method. public function getJobUniqueVia() { return Cache::driver('redis'); } Finally, note that Laravel now has a baked-in `WithoutOverlapping` job middleware that can limit the concurrent processing of a job. If that's all you're trying to achieve, then it might be worth considering using this middleware instead of the `ShouldBeUnique` trait. [#](#job-tags-and-display-name) Job tags and display name ---------------------------------------------------------- If you're using Horizon, you might be interested in providing custom tags for a job to monitor it and even change its display name. You may do this in an action by implementing the `getJobTags` and `getJobDisplayName` methods respectively. class SendTeamReportEmail { use AsAction; public function getJobTags(Team $team): array { return ['report', 'team:'.$team->id]; } public function getJobDisplayName(): string { return 'Send team report email'; } // ... } Note that you can get the job's arguments from both these methods' arguments. [#](#asserting-jobs-were-pushed) Asserting jobs were pushed ------------------------------------------------------------ When dispatching actions as job, you might want to use `Queue::fake()` to assert that a certain job was pushed on your tests. For example, this is how you would assert that a regular job was pushed. Queue::fake(); // Do something... Queue::assertPushed(SendTeamReportEmail::class); However since the action itself is wrapped inside a `JobDecorator` that acts as a job, you cannot do the same with an action. Instead, you would need to assert that a `JobDecorator` was pushed and then add a callback that ensure the `JobDecorator` is decorating your action. Queue::fake(); // Do something... Queue::assertPushed(JobDecorator::class, function (JobDecorator $job) { return $job->decorates(SendTeamReportEmail::class); }); Admittedly, this is a lot less easy to read and pretty inconvenient if we need to do this in all of our tests. That's why Laravel actions provides static helper methods on the action itself. To assert that a certain action was dispatched as a job, all you need to do is use the `assertPushed` static method directly on the action. The example above can then be rewritten like this: Queue::fake(); // Do something... SendTeamReportEmail::assertPushed(); Much cleaner isn't it? You may also provide a number to assert a job was dispatched a certain amount of times. SendTeamReportEmail::assertPushed(3); Or provide a callback to assert a job matching this condition was dispatched. The callback will receive the following four arguments: 1. The action itself. Here it would be an instance of `SendTeamReportEmail`. 2. The job's arguments. That is, the arguments you provided when calling `SendTeamReportEmail::dispatch(...)`. 3. The `JobDecorator` that decorates your action. 4. The name of the queue that was used. SendTeamReportEmail::assertPushed(function ($action, $arguments) { return ($team = $arguments[0])->hasAddon('reports'); }); Or you may use both a number of dispatch and a callback. SendTeamReportEmail::assertPushed(3, function ($action, $arguments) { return ($team = $arguments[0])->hasAddon('reports'); }); Finally, you may also use `assertNotPushed` and/or `assertPushedOn` to assert a job was not dispatched and/or that it was dispatched on a particular queue respectively. SendTeamReportEmail::assertNotPushed(); SendTeamReportEmail::assertNotPushed($callback); SendTeamReportEmail::assertPushedOn($queue); SendTeamReportEmail::assertPushedOn($queue, $numberOfDispatch); SendTeamReportEmail::assertPushedOn($queue, $callback); SendTeamReportEmail::assertPushedOn($queue, $numberOfDispatch, $callback); In the next page, we'll see [how to make our actions listen for events](./listen-for-events) . ← [Add validation to your controllers](/2.x/add-validation-to-controllers.html) [Listen for events](/2.x/listen-for-events.html) → --- # How does it work? | Laravel Actions [#](#how-does-it-work) How does it work? ========================================= [#](#decorating-your-actions) Decorating your actions ------------------------------------------------------ Whenever you use or register your action as a framework pattern — e.g. a controller, listener, etc. — your action is never directly used as these patterns. Instead, **a new decorator is created that wraps and delegate to your action**. ![Decorators diagram](/how-decorators.png) The primary reason for that is to ensure you have complete control over your PHP class. There is no need to extend the `Command` class; no need to implement your action around an API request or an event; no need to make your action a `Queueable` that has to be serializable; etc. Instead, you just focus on writing your class the way you want without any forced dependencies. Another important reason for decorating actions is to avoid conflict between patterns. If your action was directly used as a controller, job, listener and command, then these patterns would inevitably end up conflicting with one another. For example, you have middleware for both controllers and jobs but these are different types of middleware. Another example is how the action itself is executed. Controllers are constructed once and reused for every request whereas for listeners are created on demand. With decorators, you don't need to worry about any of that. You just write your action and trust that it will resolved and executed every single time, no matter if its as a controller or as a listener. [#](#identifying-how-an-action-is-being-executed) Identifying how an action is being executed ---------------------------------------------------------------------------------------------- You now know that if an action is being executed as a controller, it will be wrapped in a `ControllerDecorator`, but how does Laravel Actions knows that it is being executed as a controller in the first place? Laravel Actions does not extend nor override any core Laravel components to identify patterns. Instead it adds an interceptor inside the IoC container that analyses the `debug_backtrace` in order to identify how the action was dispatched. To make sure the interceptor is only intercepting actions, Laravel Actions uses a combination of **before resolving callbacks** and **extenders**. **Before resolving callbacks** were introduced in Laravel 8.15 and allow adding callbacks right before an `abstract` is being resolved from the container. If an `abstract` is using a trait from Laravel Actions — such as `AsController` — then we add one extender for that action. This extender is added at most once per action. **Extenders** are callback that allow you to transform what is being resolved from the container. The extender added by Laravel Actions uses `debug_backtrace` to identify how the action was dispatched. If no pattern was identified, it simply returns the action itself — meaning we're using it as an object. Laravel Actions also uses that extender to identify if the action is being mocked and returns the fake instance if it is. Note that it identifies patterns based on the traits used by the action. E.g. if an action does not use the `AsController` trait — included by default in `AsAction` — then the extender will not try to identify it as a controller. The image below provides a simplified diagram on how the extender affects the container resolution. ![Container resolution diagram](/how-resolution.png) ← [More granular traits](/2.x/granular-traits.html) [Use unified attributes](/2.x/use-unified-attributes.html) → --- # Listen for events | Laravel Actions [#](#listen-for-events) Listen for events ========================================== [#](#registering-the-listener) Registering the listener -------------------------------------------------------- The register your action as an event listener, simply register it in your `EventServiceProvider` just like any other listener. namespace App\Providers; class EventServiceProvider extends ServiceProvider { protected $listen = [\ MyEvent::class => [\ MyAction::class,\ ],\ ]; // ... } You may also use the `listen` method on the `Event` Facade to register it somewhere else. Event::listen(MyEvent::class, MyAction::class); // Note that it also works with string events. Event::listen('my_string_events.*', MyAction::class); [#](#queueable-listener) Queueable Listener -------------------------------------------- To make your listener queueable, add `implements \Illuminate\Contracts\Queue\ShouldQueue` to your action: class MyAction implements \Illuminate\Contracts\Queue\ShouldQueue ... [#](#from-listener-to-action) From listener to action ------------------------------------------------------ As usual, you may use the `asListener` method to translate the event data into a call to your `handle` method. class SendOfferToNearbyDrivers { use AsAction; public function handle(Address $source, Address $destination): void { // ... } public function asListener(TaxiRequested $event): void { $this->handle($event->source, $event->destination); } } If you're listening to string events, then the `asListener` method will receive all the event parameters as arguments. // When we dispatch that string event with some parameters. Event::dispatch('taxi.requested', [$source, $destination]); // Then the `asListener` method receives them as arguments. public function asListener(Source $source, Destination $destination): void { $this->handle($source, $destination); } Note that, in this particular case, the `asListener` could be obsolete since it has the same signature as the `handle` method and simply delegates to it. You may also register your action as a listener of many different events and use the `asListener` as a way to parse the various events into your `handle` method. public function asListener(...$parameters): void { $event = $parameters[0]; if ($event instanceof TaxiRequested) { return $this->handle($event->source, $event->destination); } if ($event instanceof FoodDeliveryRequested) { return $this->handle($event->restaurant->address, $event->destination); } $this->handle(...$parameters); } And that's all there is to it! Next, let's move on to [executing your actions as artisan commands](./execute-as-commands) . ← [Dispatch asynchronous jobs](/2.x/dispatch-jobs.html) [Execute as commands](/2.x/execute-as-commands.html) → --- # Execute as commands | Laravel Actions [#](#execute-as-commands) Execute as commands ============================================== [#](#registering-the-command) Registering the command ------------------------------------------------------ The first thing you need to do to run your action as an artisan command is to register it in your console `Kernel` just like any other command class. namespace App\Console; class Kernel extends ConsoleKernel { protected $commands = [\ UpdateUserRole::class,\ ]; // ... } Alternatively, you may auto-register commands by calling the `Actions::registerCommands()` method on one of your service providers. This will recursively look into the provided folders and automatically registers actions that have a signature defined. use Lorisleiva\Actions\Facades\Actions; // Register commands from actions in "app/Actions" (default). Actions::registerCommands(); // Register commands from actions in "app/MyCustomActionsFolder". Actions::registerCommands('app/MyCustomActionsFolder'); // Register commands from actions in multiple folders. Actions::registerCommands([\ 'app/Authentication',\ 'app/Billing',\ 'app/TeamManagement',\ ]); When registering commands that way, you might want to check that your application is running inside the console first. You may do this using `$this->app->runningInConsole()` inside your service provider. class AppServiceProvider extends ServiceProvider { public function boot() { if ($this->app->runningInConsole()) { Actions::registerCommands(); } } } [#](#command-signature-and-options) Command signature and options ------------------------------------------------------------------ Next, you need to provide a command signature to your action using the `$commandSignature` property. class UpdateUserRole { use AsAction; public string $commandSignature = 'users:update-role {user_id} {role}'; // ... } You may also provide a description and additional command options by using the following properties. class UpdateUserRole { use AsAction; public string $commandSignature = 'users:update-role {user_id} {role}'; public string $commandDescription = 'Updates the role of a given user.'; public string $commandHelp = 'Additional message displayed when using the --help option.'; public bool $commandHidden = true; // Hides the command from the artisan list. // ... } If you need to define these options using more logic, you may use the following methods instead. class UpdateUserRole { use AsAction; public function getCommandSignature(): string { return 'users:update-role {user_id} {role}'; } public function getCommandDescription(): string { return 'Updates the role of a given user.'; } public function getCommandHelp(): string { return 'Additional message displayed when using the --help option.'; } public function isCommandHidden(): bool { return true; // Hides the command from the artisan list. } // ... } [#](#from-command-to-action) From command to action ---------------------------------------------------- Finally, you will need to implement the `asCommand` method in order to parse the command's input into a call to your `handle` method. The `asCommand` method provides you with the `CommandDecorator` as a first argument which is an instance of `Illuminate\Console\Command`. This means you can use it to fetch command arguments and options but also to prompt and/or display something back to the terminal. use Illuminate\Console\Command; class UpdateUserRole { use AsAction; public string $commandSignature = 'users:update-role {user_id} {role}'; public function handle(User $user, string $newRole): void { $user->update(['role' => $newRole]); } public function asCommand(Command $command): void { $this->handle( User::findOrFail($command->argument('user_id')), $command->argument('role') ); $command->info('Done!'); } } In the example above, we've used the `{user_id}` and `{role}` arguments but we could have also prompted for these values as we run the command. class UpdateUserRole { use AsAction; public string $commandSignature = 'users:update-role'; public function handle(User $user, string $newRole): void { $user->update(['role' => $newRole]); } public function asCommand(Command $command): void { $userId = $command->ask('What is the ID of the user?'); if (! $user = User::find($userId)) { return $command->error('This user does not exists.'); } $role = $command->choice('What new role should we assign this user?', [\ 'reader', 'author', 'moderator', 'admin',\ ]); $this->handle($user, $role); $command->info('Done!'); } } We've now seen how to execute our actions in many different ways. In the next page, we'll see [how to mock them in our tests](./mock-and-test) . ← [Listen for events](/2.x/listen-for-events.html) [Mock and test your actions](/2.x/mock-and-test.html) → --- # Use unified attributes | Laravel Actions [#](#use-unified-attributes) Use unified attributes ==================================================== Info This feature is available from version `2.1`. [#](#why-using-unified-attributes) Why using unified attributes? ----------------------------------------------------------------- In the first version of Laravel Actions, actions were much more opinionated and forced you to **structure your data as a set of attributes that were unified across all design patterns** — i.e. as an object, as a controller, etc. Laravel Actions moved away from this and dropped unified attributes to allow actions to be much more flexible and much less intrusive in the way you organize your classes. As a result, you can now run any class as anything you want and even [cherry-pick the parts of Laravel Actions you want](./granular-traits) . This also means that Laravel Actions can now only help you resolve authorization and validation for your actions when they are executed as controllers. In the first version, the unified attributes meant we could offer this feature for every single pattern. For that reason, Laravel Actions v2 provides an optional `WithAttributes` trait that allows you to **structure your action's data as an array of attributes that can be validated at any time**. Additionally, this trait makes the process of upgrading from Laravel Actions v1 much easier. [#](#adding-attributes-to-an-action) Adding attributes to an action -------------------------------------------------------------------- The `WithAttributes` trait is not included in the `AsAction` trait by default. This means you will need to add it next to the other imports. use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\WithAttributes; class MyAction { use AsAction; use WithAttributes; // Or if you prefer cherry-picking patterns. use AsObject; use AsController; use AsFaker; use WithAttributes; } If you're going to use unified attributes for every single action, you might want to create your own `AsAction` trait like so. use Lorisleiva\Actions\Concerns\AsAction as AsBaseAction; use Lorisleiva\Actions\Concerns\WithAttributes; trait AsAction { use AsBaseAction; use WithAttributes; } [#](#managing-attributes) Managing attributes ---------------------------------------------- The `WithAttributes` trait provides the following methods to access and update attributes. You can [read more about them in the references](/2.x/with-attributes.html#methods-provided) . $action->setRawAttributes(['key' => 'value']); // Replace all attributes. $action->fill(['key' => 'value']); // Merge the given attributes with the existing attributes. $action->fillFromRequest($request); // Merge the request data and route parameters with the existing attributes. $action->all(); // Retrieve all attributes. $action->only('title', 'body'); // Retrieve only the attributes provided. $action->except('body'); // Retrieve all attributes excepts the one provided. $action->has('title'); // Whether the action has the provided attribute. $action->get('title'); // Get an attribute. $action->get('title', 'Untitled'); // Get an attribute with default value. $action->set('title', 'My blog post'); // Set an attribute. $action->title; // Get an attribute. $action->title = 'My blog post'; // Set an attribute. [#](#validating-attributes) Validating attributes -------------------------------------------------- The `WithAttributes` trait also provides a `validateAttributes` method that you can use at any time to trigger the authorization and validation of your attributes. This method returns the validated data if you need it. public function handle(array $attributes = []) { $this->fill($attributes); $validatedData = $this->validateAttributes(); // ... } When calling the `validateAttributes` method, the same methods used to validate the `ActionRequest` will be used to validate your attributes: * [`prepareForValidation`](/2.x/as-controller.html#prepareforvalidation) * [`authorize`](/2.x/as-controller.html#authorize) * [`rules`](/2.x/as-controller.html#rules) * [`withValidator`](/2.x/as-controller.html#withvalidator) * [`afterValidator`](/2.x/as-controller.html#aftervalidator) * [`getValidator`](/2.x/as-controller.html#getvalidator) * [`getValidationData`](/2.x/as-controller.html#getvalidationdata) * [`getValidationMessages`](/2.x/as-controller.html#getvalidationmessages) * [`getValidationAttributes`](/2.x/as-controller.html#getvalidationattributes) * [`getValidationRedirect`](/2.x/as-controller.html#getvalidationredirect) * [`getValidationErrorBag`](/2.x/as-controller.html#getvalidationerrorbag) * [`getValidationFailure`](/2.x/as-controller.html#getvalidationfailure) * [`getAuthorizationFailure`](/2.x/as-controller.html#getauthorizationfailure) Note that, when using the `WithAttributes` trait, **the action will no longer automatically validate the `ActionRequest` for you**. This is to avoid triggering the validation process twice: once on the `ActionRequest` and once on your attributes. If you want to manually trigger validation on the `ActionRequest` instance, you can do so by calling the `validate` method on the request. class MyAction { use AsAction; use WithAttributes; public function rules() { return [\ // ...\ ]; } public function asController(ActionRequest $request) { // Even though we provided some rules, the $request will // not be validated since we're using unified attributed. // You can trigger validation on the request manually like so. $request->validate(); // ... } } [#](#a-concrete-example) A concrete example -------------------------------------------- Let's have a look at a concrete example using unified attributes. We'll implement an action that creates a new article and do so as an object or as a controller. We'll want authorization and validation to trigger for both of these patterns. It's important to note that, even with the `WithAttributes` trait, you still have full control over how to structure your action's API. It's a good idea to first think about how you'd like your action to be run as an object. Do you want to be explicit in the arguments you provide? Do you want to give all the data as one big array? Or a mixture of both? // As explicit arguments. PublishNewArticle::run($author, $title, $body); // As an array of attributes. PublishNewArticle::run([\ 'author' => $author,\ 'title' => $title,\ 'body' => $body,\ ]) // As a mixture of both. PublishNewArticle::run($author, [\ 'title' => $title,\ 'body' => $body,\ ]) Since it comes down to preferences, let's use the latter so we can study both scenarios at once. Let's implement that `PublishNewArticle` action. The `authorize` method will check if the user has the appropriate permission to add articles and the `rule` method will validate the title and the content of the article. class PublishNewArticle { use AsAction; use WithAttributes; public function authorize() { return $this->author->can('publish-new-articles'); } public function rules() { return [\ 'title' => ['required'],\ 'body' => ['required', 'min:100'],\ ] } public function handle(User $author, array $attributes = []) { $this->set('author', $author)->fill($attributes); $validatedData = $this->validateAttributes(); return $this->author->articles()->create($validatedData); } public function asController(ActionRequest $request) { $this->fillFromRequest($request); return $this->handle($request->user()); } } Notice how the `handle` method fills an optional attribute array when used as an object. When used as a controller, we can use the `fillFromRequest` method instead which will fill our attributes with the request data and its route parameters. Note that there are many ways you could handle unified attributes in your actions. This example is simply meant to help you get started with unified attributes. ← [How does it work?](/2.x/how-does-it-work.html) [As object](/2.x/as-object.html) → --- # As object | Laravel Actions [#](#as-object) As object ========================== [#](#methods-provided) Methods provided ---------------------------------------- _Lists all methods provided by the trait._ ### [#](#make) `make` Resolves the action from the container. MyAction::make(); // Equivalent to: app(MyAction::class); ### [#](#run) `run` Resolves and executes the action. MyAction::run($someArguments); // Equivalent to: MyAction::make()->handle($someArguments); ### [#](#runif) `runIf` Resolves and executes the action if the condition is met. MyAction::runIf(true, $someArguments); ### [#](#rununless) `runUnless` Resolves and executes the action if some condition is not met. MyAction::runUnless(false, $someArguments); ← [Use unified attributes](/2.x/use-unified-attributes.html) [As controller](/2.x/as-controller.html) → --- # As controller | Laravel Actions [#](#as-controller) As controller ================================== [#](#methods-provided) Methods provided ---------------------------------------- _Lists all methods provided by the trait._ ### [#](#invoke) `__invoke` Executes the action by delegating immediately to the `handle` method. $action($someArguments); // Equivalent to: $action->handle($someArguments); Whilst this method is not actually used, it has to be defined on the action in order to register the action as an invokable controller. When missing, Laravel will throw an exception warning us that we're trying to register a class as an invokable controller without the `__invoke` method. The truth is, the controller will actually be an instance of `ControllerDecorator` but the framework doesn't know that yet. // Illuminate\Routing\RouteAction protected static function makeInvokable($action) { if (! method_exists($action, '__invoke')) { throw new UnexpectedValueException("Invalid route action: [{$action}]."); } return $action.'@__invoke'; } If you need to use the `__invoke` method for something else, you may [override it (opens new window)](https://stackoverflow.com/a/11939306/11440277) with anything you want. The only requirement is that an `__invoke` method has to exists. class MyAction { use AsAction { __invoke as protected invokeFromLaravelActions; } public function __invoke() { // ... } } [#](#methods-used) Methods used -------------------------------- _Lists all methods recognised and used by the `ControllerDecorator` and `ActionRequest`._ ### [#](#ascontroller) `asController` Called when used as an invokable controller. Uses the `handle` method directly when no `asController` method exists. public function asController(User $user, Request $request): Response { $article = $this->handle( $user, $request->get('title'), $request->get('body') ); return redirect()->route('articles.show', [$article]); } ### [#](#jsonresponse) `jsonResponse` Called after the `asController` method when the request expects JSON. The first argument is the return value of the `asController` method and the second argument is the request itself. public function jsonResponse(Article $article, Request $request): ArticleResource { return new ArticleResource($article); } ### [#](#htmlresponse) `htmlResponse` Called after the `asController` method when the request expects HTML. The first argument is the return value of the `asController` method and the second argument is the request itself. public function htmlResponse(Article $article, Request $request): Response { return redirect()->route('articles.show', [$article]); } ### [#](#getcontrollermiddleware) `getControllerMiddleware` Adds controller middleware directly in the action. public function getControllerMiddleware(): array { return ['auth', MyCustomMiddleware::class]; } ### [#](#routes) `routes` Defines some routes directly in your action. public static function routes(Router $router) { $router->get('author/{author}/articles', static::class); } For this to work, you need to call `Actions::registerRoutes` on a service provider. use Lorisleiva\Actions\Facades\Actions; // Register routes from actions in "app/Actions" (default). Actions::registerRoutes(); // Register routes from actions in "app/MyCustomActionsFolder". Actions::registerRoutes('app/MyCustomActionsFolder'); // Register routes from actions in multiple folders. Actions::registerRoutes([\ 'app/Authentication',\ 'app/Billing',\ 'app/TeamManagement',\ ]); ### [#](#prepareforvalidation) `prepareForValidation` Called right before authorization and validation is resolved. public function prepareForValidation(ActionRequest $request): void { $request->merge(['some' => 'additional data']); } ### [#](#authorize) `authorize` Defines authorization logic for the controller. public function authorize(ActionRequest $request): bool { return $request->user()->role === 'author'; } You may also return gate responses instead of booleans. use Illuminate\Auth\Access\Response; public function authorize(ActionRequest $request): Response { if ($request->user()->role !== 'author') { return Response::deny('You must be an author to create a new article.'); } return Response::allow(); } ### [#](#rules) `rules` Provides the validation rules for the controller. public function rules(): array { return [\ 'title' => ['required', 'min:8'],\ 'body' => ['required', IsValidMarkdown::class],\ ]; } ### [#](#withvalidator) `withValidator` Adds custom validation logic to the existing validator. use Illuminate\Validation\Validator; public function withValidator(Validator $validator, ActionRequest $request): void { $validator->after(function (Validator $validator) use ($request) { if (! Hash::check($request->get('current_password'), $request->user()->password)) { $validator->errors()->add('current_password', 'Wrong password.'); } }); } ### [#](#aftervalidator) `afterValidator` Adds an `after` callback to the existing validator. The example below is equivalent to the example provided in the `withValidator` method. use Illuminate\Validation\Validator; public function afterValidator(Validator $validator, ActionRequest $request): void { if (! Hash::check($request->get('current_password'), $request->user()->password)) { $validator->errors()->add('current_password', 'Wrong password.'); } } ### [#](#getvalidator) `getValidator` Defines your own validator instead of the default one generated using `rules`, `withValidator`, etc. use Illuminate\Validation\Factory; use Illuminate\Validation\Validator; public function getValidator(Factory $factory, ActionRequest $request): Validator { return $factory->make($request->only('title', 'body'), [\ 'title' => ['required', 'min:8'],\ 'body' => ['required', IsValidMarkdown::class],\ ]); } ### [#](#getvalidationdata) `getValidationData` Defines the data that should be used for validation. Defaults to: `$request->all()`. public function getValidationData(ActionRequest $request): array { return $request->all(); } ### [#](#getvalidationmessages) `getValidationMessages` Customises the messages of your validation rules. public function getValidationMessages(): array { return [\ 'title.required' => 'Looks like you forgot the title.',\ 'body.required' => 'Is that really all you have to say?',\ ]; } ### [#](#getvalidationattributes) `getValidationAttributes` Provides some human-friendly mapping to your request attributes. public function getValidationAttributes(): array { return [\ 'title' => 'headline',\ 'body' => 'content',\ ]; } ### [#](#getvalidationredirect) `getValidationRedirect` Customises the redirect URL when validation fails. Defaults to redirecting back to the previous page. public function getValidationRedirect(UrlGenerator $url): string { return $url->to('/my-custom-redirect-url'); } ### [#](#getvalidationerrorbag) `getValidationErrorBag` Customises the validator's error bag when validation fails. Defaults to: `default`. public function getValidationErrorBag(): string { return 'my_custom_error_bag'; } ### [#](#getvalidationfailure) `getValidationFailure` Overrides the validation failure altogether. Defaults to: `ValidationException`. public function getValidationFailure(): void { throw new MyCustomValidationException(); } ### [#](#getauthorizationfailure) `getAuthorizationFailure` Overrides the authorization failure. Defaults to: `AuthorizationException`. public function getAuthorizationFailure(): void { throw new MyCustomAuthorizationException(); } ← [As object](/2.x/as-object.html) [As job](/2.x/as-job.html) → --- # As listener | Laravel Actions [#](#as-listener) As listener ============================== [#](#methods-used) Methods used -------------------------------- _Lists all methods recognised and used by the `ListenerDecorator`._ ### [#](#aslistener) `asListener` Called when executed as an event listener. Uses the `handle` method directly when no `asListener` method exists. class SendOfferToNearbyDrivers { use AsAction; public function handle(Address $source, Address $destination): void { // ... } public function asListener(TaxiRequested $event): void { $this->handle($event->source, $event->destination); } } ← [As job](/2.x/as-job.html) [As command](/2.x/as-command.html) → --- # As command | Laravel Actions [#](#as-command) As command ============================ [#](#methods-used) Methods used -------------------------------- _Lists all methods and properties recognised and used by the `CommandDecorator`._ ### [#](#ascommand) `asCommand` Called when executed as a command. Uses the `handle` method directly when no `asCommand` method exists. use Illuminate\Console\Command; class UpdateUserRole { use AsAction; public string $commandSignature = 'users:update-role {user_id} {role}'; public function handle(User $user, string $newRole): void { $user->update(['role' => $newRole]); } public function asCommand(Command $command): void { $this->handle( User::findOrFail($command->argument('user_id')), $command->argument('role') ); $command->info('Done!'); } } ### [#](#getcommandsignature) `getCommandSignature` Defines the command signature. This is required when registering an action as a command in the console `Kernel`. You may define the signature using the `$commandSignature` property below. public function getCommandSignature(): string { return 'users:update-role {user_id} {role}'; } ### [#](#commandsignature) `$commandSignature` Same as `getCommandSignature` but as a property. public string $commandSignature = 'users:update-role {user_id} {role}'; ### [#](#getcommanddescription) `getCommandDescription` Provides a description to the command. public function getCommandDescription(): string { return 'Updates the role of a given user.'; } ### [#](#commanddescription) `$commandDescription` Same as `getCommandDescription` but as a property. public string $commandDescription = 'Updates the role of a given user.'; ### [#](#getcommandhelp) `getCommandHelp` Provides an additional message displayed when using the `--help` option. public function getCommandHelp(): string { return 'My help message.'; } ### [#](#commandhelp) `$commandHelp` Same as `getCommandHelp` but as a property. public string $commandHelp = 'My help message.'; ### [#](#iscommandhidden) `isCommandHidden` Defines whether or not we should hide the command from the artisan list. Default: `false`. public function isCommandHidden(): bool { return true; } ### [#](#commandhidden) `$commandHidden` Same as `isCommandHidden` but as a property. public bool $commandHidden = true; ← [As listener](/2.x/as-listener.html) [As fake](/2.x/as-fake.html) → --- # As job | Laravel Actions [#](#as-job) As job ==================== [#](#methods-provided) Methods provided ---------------------------------------- _Lists all methods provided by the trait._ ### [#](#dispatch) `dispatch` Dispatches a job asynchronously. SendTeamReportEmail::dispatch($team); ### [#](#dispatchif) `dispatchIf` Dispatches a job asynchronously if the condition is met. SendTeamReportEmail::dispatchIf($team->plan === 'premium', $team); ### [#](#dispatchunless) `dispatchUnless` Dispatches a job asynchronously unless the condition is met. SendTeamReportEmail::dispatchUnless($team->plan === 'free', $team); ### [#](#dispatchsync) `dispatchSync` Dispatches a job synchronously. SendTeamReportEmail::dispatchSync($team); ### [#](#dispatchnow) `dispatchNow` Dispatches a job synchronously. (Alias of `dispatchSync`). SendTeamReportEmail::dispatchNow($team); ### [#](#dispatchafterresponse) `dispatchAfterResponse` Dispatches a job synchronously but only after the response was sent to the user. SendTeamReportEmail::dispatchAfterResponse($team); ### [#](#makejob) `makeJob` Creates a new `JobDecorator` that wraps the action. This can be used to dispatch a job using `dispatch` helper method or when creating a chain of jobs from actions (See `withChain`). dispatch(SendTeamReportEmail::makeJob($team)); ### [#](#makeuniquejob) `makeUniqueJob` Creates a new `UniqueJobDecorator` that wraps the action. By default, `makeJob` will automatically return a `UniqueJobDecorator` if your action implements the `ShouldBeUnique` trait. However, you may use this method directly to force a `UniqueJobDecorator` to be created. dispatch(SendTeamReportEmail::makeUniqueJob($team)); ### [#](#withchain) `withChain` Attaches a list of jobs to be executed after the job was processed. $chain = [\ OptimizeTeamReport::makeJob($team),\ SendTeamReportEmail::makeJob($team),\ ]; CreateNewTeamReport::withChain($chain)->dispatch($team); Note that you can achieve the same result by using the chain method on the Bus Facade. use Illuminate\Support\Facades\Bus; Bus::chain([\ CreateNewTeamReport::makeJob($team),\ OptimizeTeamReport::makeJob($team),\ SendTeamReportEmail::makeJob($team),\ ])->dispatch(); ### [#](#assertpushed) `assertPushed` Asserts the action was dispatched. // Requires the Queue Facade to be fake. Queue::fake(); // Assert the job was dispatched. SendTeamReportEmail::assertPushed(); // Assert the job was dispatched 3 times. SendTeamReportEmail::assertPushed(3); // Assert a job that satisfies the given callback was dispatched. SendTeamReportEmail::assertPushed($callback); // Assert a job that satisfies the given callback was dispatched 3 times. SendTeamReportEmail::assertPushed(3, $callback); The callback will receive the following four arguments: 1. The action itself. Here it would be an instance of SendTeamReportEmail. 2. The job's arguments. That is, the arguments you provided when calling SendTeamReportEmail::dispatch(...). 3. The JobDecorator that decorates your action. 4. The name of the queue that was used. ### [#](#assertnotpushed) `assertNotPushed` Asserts the action was not dispatched. See `assertPushed` for the callback arguments. // Requires the Queue Facade to be fake. Queue::fake(); // Assert the job was not dispatched. SendTeamReportEmail::assertNotPushed(); // Assert a job that satisfies the given callback was not dispatched. SendTeamReportEmail::assertNotPushed($callback); ### [#](#assertpushedon) `assertPushedOn` Asserts the action was dispatched on a given queue. See `assertPushed` for the callback arguments. // Requires the Queue Facade to be fake. Queue::fake(); // Assert the job was dispatched on the 'reports' queue. SendTeamReportEmail::assertPushedOn('reports'); // Assert the job was dispatched on the 'reports' queue 3 times. SendTeamReportEmail::assertPushedOn('reports', 3); // Assert a job that satisfies the given callback was dispatched on the 'reports' queue. SendTeamReportEmail::assertPushedOn('reports', $callback); // Assert a job that satisfies the given callback was dispatched on the 'reports' queue 3 times. SendTeamReportEmail::assertPushedOn('reports', 3, $callback); [#](#methods-used) Methods used -------------------------------- _Lists all methods and properties recognised and used by the `JobDecorator`._ ### [#](#asjob) `asJob` Called when dispatched as a job. Uses the `handle` method directly when no `asJob` method exists. class SendTeamReportEmail { use AsAction; public function handle(Team $team, bool $fullReport = false): void { // Prepare report and send it to all $team->users. } public function asJob(Team $team): void { $this->handle($team, true); } } ### [#](#getjobmiddleware) `getJobMiddleware` Adds job middleware directly in the action. The parameters are passed in but note that they are in an array. public function getJobMiddleware(array $parameters): array { return [new RateLimited('reports')]; } ### [#](#configurejob) `configureJob` Defines the `JobDecorators`'s option directly in the action. use Lorisleiva\Actions\Decorators\JobDecorator; public function configureJob(JobDecorator $job): void { $job->onConnection('my_connection') ->onQueue('my_queue') ->through(['my_middleware']) ->chain(['my_chain']) ->delay(60); } ### [#](#jobconnection) `$jobConnection` Defines the connection of the `JobDecorator`. Can also be set using `configureJob`. public string $jobConnection = 'my_connection'; ### [#](#jobqueue) `$jobQueue` Defines the queue of the `JobDecorator`. Can also be set using `configureJob`. public string $jobQueue = 'my_queue'; ### [#](#jobtries) `$jobTries` Defines the number of times the job may be attempted. public int $jobTries = 10; ### [#](#jobmaxexceptions) `$jobMaxExceptions` Defines the maximum number of exceptions to allow before failing. public int $jobMaxExceptions = 3; ### [#](#jobbackoff) `$jobBackoff` Defines the number of seconds to wait before retrying the job. Can also be set the `getJobBackoff` method. public int $jobBackoff = 60; ### [#](#getjobbackoff) `getJobBackoff` Defines the number of seconds to wait before retrying the job. public function getJobBackoff(): int { return 60; } You may also provide an array to provide different backoffs for each retries. public function getJobBackoff(): array { return [30, 60, 120]; } ### [#](#jobtimeout) `$jobTimeout` Defines the number of seconds the job can run before timing out. public int $jobTimeout = 60 * 30; ### [#](#jobretryuntil) `$jobRetryUntil` Defines the timestamp at which the job should timeout. Can also be set the `getJobRetryUntil` method. public int $jobRetryUntil = 1610191764; ### [#](#getjobretryuntil) `getJobRetryUntil` Defines the time at which the job should timeout. public function getJobRetryUntil(): DateTime { return now()->addMinutes(30); } ### [#](#getjobdisplayname) `getJobDisplayName` Customises the display name of the `JobDecorator`. It provides the same arguments as the `asJob` method. public function getJobDisplayName(): string { return 'Send team report email'; } ### [#](#getjobtags) `getJobTags` Adds some tags to the `JobDecorator`. It provides the same arguments as the `asJob` method. public function getJobTags(Team $team): array { return ['report', 'team:'.$team->id]; } ### [#](#getjobuniqueid) `getJobUniqueId` Defines the unique key when using the `ShouldBeUnique` interface. It provides the same arguments as the `asJob` method. public function getJobUniqueId(Team $team) { return $team->id; } ### [#](#jobuniqueid) `$jobUniqueId` Same as `getJobUniqueId` but as a property. public string $jobUniqueId = 'some_static_key'; ### [#](#getjobuniquefor) `getJobUniqueFor` Define the amount of time in which a job should stay unique when using the `ShouldBeUnique` interface. It provides the same arguments as the `asJob` method. public function getJobUniqueFor(Team $team) { return $this->team->role === 'premium' ? 1800 : 3600; } ### [#](#jobuniquefor) `$jobUniqueFor` Same as `getJobUniqueFor` but as a property. public int $jobUniqueFor = 3600; ### [#](#getjobuniquevia) `getJobUniqueVia` Defines the cache driver to use to obtain the lock and therefore maintain the unicity of the jobs being dispatched. Defaults to: the default cache driver. public function getJobUniqueVia() { return Cache::driver('redis'); } ### [#](#jobdeletewhenmissingmodels) `$jobDeleteWhenMissingModels` Same as `getJobDeleteWhenMissingModels` but as a property. public bool $jobDeleteWhenMissingModels = true; ### [#](#getjobdeletewhenmissingmodels) `getJobDeleteWhenMissingModels` Defines whether to automatically delete jobs with missing models. public function getJobDeleteWhenMissingModels(): bool { return true; } ### [#](#jobfailed) `jobFailed` Handle the job failure, if an exception is thrown it is passed in as the first arguent, the parameters the job was called with are spread into the rest of the arguments. public function jobFailed(?Throwable, ...$parameters): void { // Send user notification of failure, etc... } ← [As controller](/2.x/as-controller.html) [As listener](/2.x/as-listener.html) → --- # As fake | Laravel Actions [#](#as-fake) As fake ====================== [#](#methods-provided) Methods provided ---------------------------------------- _Lists all methods provided by the trait._ ### [#](#mock) `mock` Swaps the action with a mock. FetchContactsFromGoogle::mock() ->shouldReceive('handle') ->with(42) ->andReturn(['Loris', 'Will', 'Barney']); ### [#](#partialmock) `partialMock` Swaps the action with a partial mock. In the example below, only the `fetch` method is mocked. FetchContactsFromGoogle::partialMock() ->shouldReceive('fetch') ->with('some_google_identifier') ->andReturn(['Loris', 'Will', 'Barney']); ### [#](#spy) `spy` Swaps the action with a spy. $spy = FetchContactsFromGoogle::spy() ->allows('handle') ->andReturn(['Loris', 'Will', 'Barney']); // ... $spy->shouldHaveReceived('handle')->with(42); ### [#](#shouldrun) `shouldRun` Helper method adding an expectation on the `handle` method. FetchContactsFromGoogle::shouldRun(); // Equivalent to: FetchContactsFromGoogle::mock()->shouldReceive('handle'); ### [#](#shouldnotrun) `shouldNotRun` Helper method adding an expectation on the `handle` method. FetchContactsFromGoogle::shouldNotRun(); // Equivalent to: FetchContactsFromGoogle::mock()->shouldNotReceive('handle'); ### [#](#allowtorun) `allowToRun` Helper method allowing the `handle` method on a spy. $spy = FetchContactsFromGoogle::allowToRun() ->andReturn(['Loris', 'Will', 'Barney']); // ... $spy->shouldHaveReceived('handle')->with(42); ### [#](#isfake) `isFake` Whether the action has been swapped for a fake instance. FetchContactsFromGoogle::isFake(); // false FetchContactsFromGoogle::mock(); FetchContactsFromGoogle::isFake(); // true ### [#](#clearfake) `clearFake` Clear the action's fake instance if any. FetchContactsFromGoogle::mock(); FetchContactsFromGoogle::isFake(); // true FetchContactsFromGoogle::clearFake(); FetchContactsFromGoogle::isFake(); // false ← [As command](/2.x/as-command.html) [With attributes](/2.x/with-attributes.html) → --- # With attributes | Laravel Actions [#](#with-attributes) With attributes ====================================== [#](#methods-provided) Methods provided ---------------------------------------- _Lists all methods provided by the trait._ ### [#](#setrawattributes) `setRawAttributes` Replaces all attributes with the provided attributes. $action->setRawAttributes([\ 'key' => 'value',\ ]); ### [#](#fill) `fill` Merges the provided attributes with the existing attributes. $action->fill([\ 'key' => 'value',\ ]); ### [#](#fillfromrequest) `fillFromRequest` Merges the request data and its route parameters with the existing attributes. If an attribute is present in both the request data and as a route parameter, the request data takes priority. $action->fillFromRequest($request); ### [#](#all) `all` Retrieves all attributes. $action->all(); ### [#](#only) `only` Retrieves all attributes such that their key is included in the arguments. $action->only('title', 'body'); ### [#](#except) `except` Retrieves all attributes except those whose key is included in the arguments. $action->except('body'); ### [#](#has) `has` Returns `true` if and only if there is an existing attribute with the provided key. $action->has('title'); ### [#](#get) `get` Retrieves the value of an attribute by passing its key as the first argument. An optional second argument can be passed to provide a default value should the attribute not exist on the action. $action->get('title'); $action->get('title', 'Untitled'); ### [#](#set) `set` Set the value of an attribute by providing a key and its value. $action->set('title', 'My blog post'); ### [#](#get-2) `__get` Makes attributes accessible as properties by using the magic method `__get`. $action->title; ### [#](#set-2) `__set` Allows attributes to be updated like properties by using the magic method `__set`. $action->title = 'My blog post'; ### [#](#isset) `__isset` Allows attributes' existence to be checked like properties by using the magic method `__isset`. isset($action->title); ### [#](#validateattributes) `validateAttributes` Triggers the authorization and validation process on the action using its attributes and returns the validated data. [See the guide for more information](/2.x/use-unified-attributes.html#validating-attributes) . $validatedData = $action->validateAttributes(); [#](#methods-used) Methods used -------------------------------- _Lists all methods recognised and used by the `AttributeValidator`._ The `WithAttributes` trait uses the same authorization and validation methods used by the `AsController` trait: * [`prepareForValidation`](/2.x/as-controller.html#prepareforvalidation) * [`authorize`](/2.x/as-controller.html#authorize) * [`rules`](/2.x/as-controller.html#rules) * [`withValidator`](/2.x/as-controller.html#withvalidator) * [`afterValidator`](/2.x/as-controller.html#aftervalidator) * [`getValidator`](/2.x/as-controller.html#getvalidator) * [`getValidationData`](/2.x/as-controller.html#getvalidationdata) * [`getValidationMessages`](/2.x/as-controller.html#getvalidationmessages) * [`getValidationAttributes`](/2.x/as-controller.html#getvalidationattributes) * [`getValidationRedirect`](/2.x/as-controller.html#getvalidationredirect) * [`getValidationErrorBag`](/2.x/as-controller.html#getvalidationerrorbag) * [`getValidationFailure`](/2.x/as-controller.html#getvalidationfailure) * [`getAuthorizationFailure`](/2.x/as-controller.html#getauthorizationfailure) ← [As fake](/2.x/as-fake.html) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Installation | Laravel Actions [#](#installation) Installation ================================ All you need to do to get started is add Laravel Action to your composer dependencies. composer require "lorisleiva/laravel-actions":"^1.2" You can then use the `make:action` artisan command to create your first action. php artisan make:action MyFirstAction This will create the following class in your repository. namespace App\Actions; use Lorisleiva\Actions\Action; class MyFirstAction extends Action { /** * Determine if the user is authorized to make this action. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the action. * * @return array */ public function rules() { return []; } /** * Execute the action and return a result. * * @return mixed */ public function handle() { // Execute the action. } } ← [Introduction](/1.x/) [Basic usage](/1.x/basic-usage.html) → --- # Basic usage | Laravel Actions [#](#basic-usage) Basic usage ============================== Create your first action using `php artisan make:action PublishANewArticle` and fill the authorisation logic, the validation rules and the handle method. Note that the `authorize` and `rules` methods are optional and default to `true` and `[]` respectively. // app/Actions/PublishANewArticle.php class PublishANewArticle extends Action { public function authorize() { return $this->user()->hasRole('author'); } public function rules() { return [\ 'title' => 'required',\ 'body' => 'required|min:10',\ ]; } public function handle() { return Article::create($this->validated()); } } You can now start using that action in multiple ways: [#](#as-a-plain-object) As a plain object. ------------------------------------------- $action = new PublishANewArticle([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); $article = $action->run(); [#](#as-a-dispatchable-job) As a dispatchable job. --------------------------------------------------- PublishANewArticle::dispatch([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); [#](#as-an-event-listener) As an event listener. ------------------------------------------------- class ProductCreated { public $title; public $body; public function __construct($title, $body) { $this->title = $title; $this->body = $body; } } Event::listen(ProductCreated::class, PublishANewArticle::class); event(new ProductCreated('My new SaaS application', 'Lorem Ipsum.')); [#](#as-an-invokable-controller) As an invokable controller. ------------------------------------------------------------- // routes/web.php Route::post('articles', '\App\Actions\PublishANewArticle'); If you need to specify an explicit HTTP response for when an action is used as a controller, you can define the `response` method which provides the result of the `handle` method as the first argument. class PublishANewArticle extends Action { // ... public function response($article) { return redirect()->route('article.show', $article); } } [#](#as-an-artisan-command) As an artisan command. --------------------------------------------------- class PublishANewArticle extends Action { protected static $commandSignature = 'make:article {title} {body}'; protected static $commandDescription = 'Publishes a new article'; // ... } ← [Installation](/1.x/installation.html) [Actions' attributes](/1.x/actions-attributes.html) → --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Actions' attributes | Laravel Actions [#](#actions-attributes) Actions' attributes ============================================= In order to unify the various forms an action can take, the data of an action is implemented as a set of attributes (similarly to models). This means when interacting with an instance of an `Action`, you can manipulate its attributes with the following methods: $action = new Action(['key' => 'value']); // Initialise an action with the provided attribute. $action->fill(['key' => 'value']); // Merge the new attributes with the existing attributes. $action->all(); // Retrieve all attributes of an action as an array. $action->only('title', 'body'); // Retrieve only the attributes provided. $action->except('body'); // Retrieve all attributes excepts the one provided. $action->has('title'); // Whether the action has the provided attribute. $action->get('title'); // Get an attribute. $action->get('title', 'Untitled'); // Get an attribute with default value. $action->set('title', 'My blog post'); // Set an attribute. $action->title; // Get an attribute. $action->title = 'My blog post'; // Set an attribute. Depending on how an action is ran, its attributes are filled with the relevant information available. For example, as a controller, an action’s attributes will contain all of the input from the request. For more information see: * [How do attributes get filled as objects?](/1.x/actions-as-objects.html) * [How do attributes get filled as jobs?](/1.x/actions-as-jobs.html) * [How do attributes get filled as listeners?](/1.x/actions-as-listeners.html) * [How do attributes get filled as controllers?](/1.x/actions-as-controllers.html) * [How do attributes get filled as commands?](/1.x/actions-as-commands.html) ← [Basic usage](/1.x/basic-usage.html) [Dependency injections](/1.x/dependency-injections.html) → --- # Dependency injections | Laravel Actions [#](#dependency-injections) Dependency injections ================================================== The `handle` method support dependency injections. That means, whatever arguments you enter in the `handle` method, Laravel Actions will try to resolve them from the container but also from its own attributes. Let’s have a look at some examples. // Resolved from the IoC container. public function handle(Request $request) {/* ... */} public function handle(MyService $service) {/* ... */} // Resolved from the attributes. // -- $title and $body are equivalent to $action->title and $action->body // -- When attributes are missing, null will be returned unless a default value is provided. public function handle($title, $body) {/* ... */} public function handle($title, $body = 'default') {/* ... */} // Resolved from the attributes using route model binding. // -- If $action->comment is already an instance of Comment, it provides it. // -- If $action->comment is an id, it will provide the right instance of // Comment from the database or fail (just like it would in a controller). // This will also update $action->comment to be that instance. public function handle(Comment $comment) {/* ... */} // They can all be combined (in no particular order). public function handle($title, Comment $comment, MyService $service) {/* ... */} As you can see, both the action’s attributes and the IoC container are used to resolve dependency injections. When a matching attribute is type-hinted, the library will do its best to provide an instance of that class from the value of the attribute. TIP Note that almost every method that are meant for you to override (e.g. `authorize`, `rules`, `handle`, etc.) support dependency injections. ← [Actions' attributes](/1.x/actions-attributes.html) [Authorisation](/1.x/authorisation.html) → --- # Validation | Laravel Actions [#](#validation) Validation ============================ No matter how an action is run, you can define some validation logic directly within the action to ensure the provided attributes are what they are expected to be. Note that, just like in a Laravel FormRequest, the validation logic occurs after the authorisation logic ([see previous page](/1.x/authorisation.html) ). TIP The page "[The lifecycle of an action](/1.x/action-lifecycle.html) " provides a handy summary of all methods that an Action will call before and after executing the `handle` method. [#](#defining-the-validation-rules) Defining the validation rules ------------------------------------------------------------------ Just like in a Laravel FormRequest, you can define your validation logic using the `rules` and `withValidator` methods. The `rules` method enables you to list validation rules for your action's attributes. public function rules() { return [\ 'title' => ['required'],\ 'body' => ['required', 'min:10'],\ ]; } The `withValidator` method provide a convenient way to add custom validation logic. public function withValidator($validator) { $validator->after(function ($validator) { if ($this->somethingElseIsInvalid()) { $validator->errors()->add('field', 'Something is wrong with this field!'); } }); } If all you want to do is add an after validation hook, you can use the `afterValidator` method instead of the `withValidator` method. The following example is equivalent to the one above. public function afterValidator($validator) { if ($this->somethingElseIsInvalid()) { $validator->errors()->add('field', 'Something is wrong with this field!'); }; } TIP It is worth noting that, just like the `handle` method, the `rules`, `withValidator` and `afterValidator` methods [support dependency injections](/1.x/dependency-injections.html) . If you want to validate some data directly within the `handle` method, you can use the `validate` method. public function handle() { $this->validate([\ 'comment' => ['required', 'min:10', 'spamfree'],\ ]); } This will validate the provided rules against the action's attributes. [#](#handling-failed-validation) Handling failed validation ------------------------------------------------------------ Whenever validation fails, it will throw an `ValidationException` resulting in a 422 status code when used in the HTTP layer (as a controller for example). You can change that behaviour by overriding the `failedValidation` method. protected function failedValidation() { throw new MyCustomExceptionForInvalidData(); } [#](#accessing-validated-data) Accessing validated data -------------------------------------------------------- If you want to access all attributes that have been validated prior to reaching the `handle` method, you can use `$this->validated()` instead of `$this->all()`. public function rules() { return ['title' => 'min:3']; } public function handle() { // Will only return attributes that have been validated by the rules above. $this->validated(); } [#](#customising-validation-texts) Customising validation texts ---------------------------------------------------------------- You can customise the validation texts using the `messages` and `attributes` methods. public function messages() { return []; } public function attributes() { return []; } TIP It is worth noting that, just like the `handle` method, the `messages`, `attributes` methods [support dependency injections](/1.x/dependency-injections.html) . ← [Authorisation](/1.x/authorisation.html) [Actions as objects](/1.x/actions-as-objects.html) → --- # Actions as objects | Laravel Actions [#](#actions-as-objects) Actions as objects ============================================ [#](#how-are-attributes-filled) How are attributes filled? ----------------------------------------------------------- By default, you can initialise an action by providing an array of attributes like this. $action = new PublishANewArticle([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); You can define your custom logic of how to get attributes from the constructor by overriding the `getAttributesFromConstructor` method. class PublishANewArticle extends Action { public function getAttributesFromConstructor($title, $body) { return compact('title', 'body'); } } With the example above, you can now create a new `PublishANewArticle` action like this: $action = new PublishANewArticle('My blog post', 'Lorem ipsum.'); If (like in the example above) you simply need to map the order in which the attributes are given, you can achieve the exact same result by defining the `$getAttributesFromConstructor` property instead. class PublishANewArticle extends Action { protected $getAttributesFromConstructor = ['title', 'body']; } Finally, if you want to use the same order as the arguments of the handle method, all you need to do is set the `$getAttributesFromConstructor` property to `true`. The code below is equivalent to the previous example. class PublishANewArticle extends Action { protected $getAttributesFromConstructor = true; public function handle($title, $body) { // ... } } [#](#the-static-run-method) The static `run` method ---------------------------------------------------- In most cases, you will likely want to create an action and run it immediately. Thus, there exists a static `run` method that does just that. It is worth noting that any argument given to the static `run` method will be passed on to the constructor. // This: PublishANewArticle::run('My blog post', 'Lorem ipsum.'); // Is equivalent to this: $action = new PublishANewArticle('My blog post', 'Lorem ipsum.'); $action->run(); TIP If you are using PHPStorm, you can use annotations to update the signature of the static run method. That way, you can still benefit from the autocompletion feature. /** * @method mixed run(string $title, string $description) */ class MyAction extends Action {...} [#](#handling-attributes) Handling attributes ---------------------------------------------- When running actions as plain PHP objects, their attributes can be manually updated using the various helper methods mentioned in the "[Actions' attributes](/1.x/actions-attributes.html) " page. For example: $action = new PublishANewArticle; $action->title = 'My blog post'; $action->set('body', 'Lorem ipsum.'); $action->run(); Note that the `run` method can also accept an array of attributes to merge with the existing attributes. $action = new PublishANewArticle; $action->fill(['title' => 'My blog post']) $action->run(['body' => 'Lorem ipsum.']); Also note that actions are invokable object which means you can run them like functions. $action = new PublishANewArticle([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); $actions(); [#](#accessing-the-returned-value) Accessing the returned value ---------------------------------------------------------------- Whatever is returned by the `handle` method will be returned by the `run` method. // Assuming the `PublishANewArticle` returns the article that // has been published, then we can access it like this. $article = PublishANewArticle::run([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); ← [Validation](/1.x/validation.html) [Actions as jobs](/1.x/actions-as-jobs.html) → --- # Authorisation | Laravel Actions [#](#authorisation) Authorisation ================================== No matter how actions are run, you can define some authorisation logic directly within the action to make sure that it is performed under valid circumstances. For example, this could ensure the authenticated user has the appropriate role before continuing. Note that, just like in a Laravel FormRequest, the authorisation logic occurs before the validation logic ([see next page](/1.x/validation.html) ). TIP The page "[The lifecycle of an action](/1.x/action-lifecycle.html) " provides a handy summary of all methods that an Action will call before and after executing the `handle` method. [#](#the-authorize-method) The `authorize` method -------------------------------------------------- Actions can define their authorisation logic using the `authorize` method. It should return a boolean indicating if we are authorised to execute this action. public function authorize() { // Your authorisation logic here... } The `authorize` method is optional and defaults to `true` when not provided. TIP It is worth noting that, just like the `handle` method, the `authorize` method [supports dependency injections](/1.x/dependency-injections.html) . Whenever the `authorize` method returns `false`, it will throw an `AuthorizationException` resulting in a 403 status code when used in the HTTP layer (as a controller for example). You can change that behaviour by overriding the `failedAuthorization` method. protected function failedAuthorization() { throw new MyCustomExceptionForWhenAnActionIsUnauthorized(); } [#](#the-user-and-actingas-methods) The `user` and `actingAs` methods ---------------------------------------------------------------------- If you want to access the authenticated user from an action you can simply use the `user` method. public function authorize() { return $this->user()->isAdmin(); } When run as a controller, the user is fetched from the incoming request, otherwise `$this->user()` is equivalent to `Auth::user()`. If you want to run an action acting on behalf of another user you can use the `actingAs` method. In this case, the `user` method will always return the provided user. $action->actingAs($admin)->run(); [#](#the-can-method) The `can` method -------------------------------------- If you’d still like to use Gates and Policies to externalise your authorisation logic, you can use the `can` helper method to verify that the user can perform the provided ability. public function authorize() { return $this->can('create', Article::class); } ← [Dependency injections](/1.x/dependency-injections.html) [Validation](/1.x/validation.html) → --- # Actions as controllers | Laravel Actions [#](#actions-as-controllers) Actions as controllers ==================================================== [#](#how-are-attributes-filled) How are attributes filled? ----------------------------------------------------------- By default, all input data from the request and the route parameters will be used to fill the action's attributes. You can change this behaviour by overriding the `getAttributesFromRequest` method. This is its default implementation: public function getAttributesFromRequest(Request $request) { return array_merge( $this->getAttributesFromRoute($request), $request->all() ); } Note that, since we're merging two sets of data together, a conflict is possible when a variable is defined on both sets. As you can see, by default, the request's data takes priority over the route's parameters. However, when resolving dependencies for the `handle` method's argument, the route parameters will take priority over the request's data. That means in case of conflict, you can access the route parameter as a method argument and the request's data as an attribute. For example: // Route endpoint: PATCH /comments/{comment} // Request input: ['comment' => 'My updated comment'] public function handle(Comment $comment) { $comment; // <- Comment instance matching the given id. $this->comment; // <- 'My updated comment' } [#](#registering-actions-in-routes-files) Registering actions in routes files ------------------------------------------------------------------------------ Because your actions are located by default in the `\App\Action` namespace and not the `\App\Http\Controller` namespace, you have to provide the full qualified name of the action if you want to define them in your `routes/web.php` or `routes/api.php` files. // routes/web.php Route::post('articles', '\App\Actions\PublishANewArticle'); _Note that the initial `\` here is important to ensure the namespace does not become `\App\Http\Controller\App\Actions\PublishANewArticle`._ Alternatively you can place them in a group that re-defines the namespace. // routes/web.php Route::namespace('\App\Actions')->group(function () { Route::post('articles', 'PublishANewArticle'); }); Laravel Actions provides a `Route` macro that does exactly this: // routes/web.php Route::actions(function () { Route::post('articles', 'PublishANewArticle'); }); Another solution would be to create a new route file `routes/action.php` and register it in your `RouteServiceProvider`. // app/Providers/RouteServiceProvider.php Route::middleware('web') ->namespace('App\Actions') ->group(base_path('routes/action.php')); // routes/action.php Route::post('articles', 'PublishANewArticle'); [#](#registering-routes-directly-in-actions) Registering routes directly in actions ------------------------------------------------------------------------------------ Another way to register actions as controllers is to define the routes directly within the actions using the static `routes` method. class GetArticlesFromAuthor extends Action { public static function routes(Router $router) { $router->get('author/{author}/articles', static::class); } public function handle(User $author) { return $author->articles; } } WARNING This will work out-of-the-box for actions defined in the `app/Actions` folder (or subfolders). If some of your actions live outside this folder, you will need to call `Actions::paths([...])` in a service provider to let the library know where to find your actions in order to register these routes. See the "[Registering actions](/1.x/registering-actions.html) " page for more details. [#](#registering-middleware) Registering middleware ---------------------------------------------------- You can register middleware using the `middleware` method. public function middleware() { return ['auth']; } Alternatively, if you decide to register your routes directly within your actions, you can specify the middleware in the `routes` static method. public static function routes(Router $router) { $router->middleware(['web', 'auth'])->get('author/{author}/articles', static::class); } [#](#returning-http-responses) Returning HTTP responses -------------------------------------------------------- It is good practice to let the action return a value that makes sense for your domain. For example, the article that was just created or the filtered list of topics that we are searching for. However, you might want to wrap that value into a proper HTTP response when the action is being ran as a controller. You can use the `response` method for that. It provides the result of the `handle` method as first argument and the HTTP request as second argument. public function response($result, $request) { return view('articles.index', [\ 'articles' => $result,\ ]) } If you want to return distinctive responses for clients that require HTML and clients that require JSON, you can respectively use the `htmlResponse` and `jsonResponse` methods. public function htmlResponse($result, $request) { return view('articles.index', [\ 'articles' => $result,\ ]); } public function jsonResponse($result, $request) { return ArticleResource::collection($result); } ← [Actions as listeners](/1.x/actions-as-listeners.html) [Actions as commands](/1.x/actions-as-commands.html) → --- # Actions as jobs | Laravel Actions [#](#actions-as-jobs) Actions as jobs ====================================== [#](#how-are-attributes-filled) How are attributes filled? ----------------------------------------------------------- Similarly to [actions as objects](/1.x/actions-as-objects.html) , attributes are filled manually when you dispatch the action. PublishANewArticle::dispatch([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); If you have defined a `getAttributesFromConstructor` method or property, this will also be applicable to jobs. For example: // If you have the following constructor mapping. class PublishANewArticle extends Action { protected $getAttributesFromConstructor = ['title', 'body']; } // Then you can dispatch the action as a job like this. PublishANewArticle::dispatch('My blog post', 'Lorem ipsum.'); [#](#queueable-actions) Queueable actions ------------------------------------------ Just like jobs, actions can be queued by implementing the `ShouldQueue` interface. use Illuminate\Contracts\Queue\ShouldQueue; class PublishANewArticle extends Action implements ShouldQueue { // ... } Note that you can also use the `dispatchNow` method to force a queueable action to be executed immediately. [#](#accessing-the-returned-value) Accessing the returned value ---------------------------------------------------------------- When dispatching a job immediately (either by using `dispatchNow` or the `sync` queue driver), then the result of the action will be returned. $article = PublishANewArticle::dispatchNow([\ 'title' => 'My blog post',\ 'body' => 'Lorem ipsum.',\ ]); [#](#registering-middleware) Registering middleware ---------------------------------------------------- You can register job middleware using the `middleware` method. public function middleware() { return [new RateLimited]; } ← [Actions as objects](/1.x/actions-as-objects.html) [Actions as listeners](/1.x/actions-as-listeners.html) → --- # Actions as listeners | Laravel Actions [#](#actions-as-listeners) Actions as listeners ================================================ [#](#how-are-attributes-filled) How are attributes filled? ----------------------------------------------------------- By default, all of the event’s public properties will be used as attributes. class ProductCreated { public $title; public $body; // ... } You can override that behaviour by defining the `getAttributesFromEvent` method. // Event class ProductCreated { public $product; } // Listener class PublishANewArticle extends Action { public function getAttributesFromEvent($event) { return [\ 'title' => '[New product] ' . $event->product->name,\ 'body' => $event->product->description,\ ]; } } This can also work with events defined as strings. // Event Event::listen('product_created', PublishANewArticle::class); // Dispatch event('product_created', ['My SaaS app', 'Lorem ipsum']); // Listener class PublishANewArticle extends Action { public function getAttributesFromEvent($title, $description) { return [\ 'title' => "[New product] $title",\ 'body' => $description,\ ]; } // ... } ← [Actions as jobs](/1.x/actions-as-jobs.html) [Actions as controllers](/1.x/actions-as-controllers.html) → --- # Actions as commands | Laravel Actions [#](#actions-as-commands) Actions as commands ============================================== [#](#how-are-attributes-filled) How are attributes filled? ----------------------------------------------------------- By default, all of the command's arguments and options will be used as attributes. This means the attributes will also include any options defined by Laravel such as `verbose` or `help`. You can provide more thorough mapping of your command input by overriding the `getAttributesFromCommand` method. class PublishANewArticle extends Action { protected static $commandSignature = 'make:article {title : The title of the article} {--published : Whether the article should be immediately published} {--tags=* : Tags for the newly created article}'; public function getAttributesFromCommand(Command $command): array { return [\ 'title' => $command->argument('title'),\ 'published_at' => $command->option('published') ? now() : null,\ 'tags' => $command->option('tags'),\ ]; } // ... } [#](#registering-commands) Registering commands ------------------------------------------------ By default, any action that defines the `$commandSignature` static property will be registered as a command. WARNING This will work out-of-the-box for actions defined in the `app/Actions` folder (or subfolders). If some of your actions live outside this folder, you will need to call `Actions::paths([...])` in a service provider to let the library know where to find your actions in order to register these routes. See the "[Registering actions](/1.x/registering-actions.html) " page for more details. Alternatively, when not automatically registered, you can manually register an action by calling the `registerCommand` static method in your `routes/console.php` file. PublishANewArticle::registerCommand(); [#](#prompting-additional-data) Prompting additional data ---------------------------------------------------------- In some cases, when an action is running as a command, you might want to gather some attributes interactively. For example, prompt the user some additional data or ask them to confirm an action before continuing. You may do all of this within the `asCommand` method. This method will run before executing the action to allow you to define additional attributes. See example below. class PublishANewArticle extends Action { protected static $commandSignature = 'make:article'; public function asCommand(Command $command) { $this->title = $command->ask('What is the title?'); $this->published_at = $command->confirm('Publish immediately?') ? now() : null; if (! $command->confirm('Are you sure?')) { throw new Exception('Operation cancelled'); } } // ... } TIP It is worth noting that, just like the `handle` method, the `asCommand` method [support dependency injections](/1.x/dependency-injections.html) . TIP Also note that there exists a `as...` method for every way an action can run as. Namely, `asObject`, `asJob`, `asListener`, `asController` and `asCommand`. See "[The before hooks](/1.x/action-running-as.html#the-before-hooks) " for more details. [#](#console-output-and-exit-code) Console output and exit code ---------------------------------------------------------------- By default, the returned value of the `handle` method will be dumped in the console and a successful exit code (i.e. `0`) will be returned. You may customise what you want to display to the console after executing the action by overriding the `consoleOutput` method. The returned value of the `handle` method is given as the first argument. The command object is available as the second argument. public function consoleOutput($article, Command $command) { $command->line("Success! You've published the article \"{$article->title}\"."); } Any value returned by the `consoleOutput` method will be used as an exit code. public function consoleOutput($article, Command $command) { return 42; } ← [Actions as controllers](/1.x/actions-as-controllers.html) [Registering actions](/1.x/registering-actions.html) → --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Registering actions | Laravel Actions [#](#registering-actions) Registering actions ============================================== [#](#why-do-actions-need-to-be-registered) Why do actions need to be registered? --------------------------------------------------------------------------------- Some features that Laravel Actions supports require them to be registered globally. These features are the following. * Registering actions as commands automatically if a signature is provided. * Registering routes directly within actions. Thus, for these features to work, we need to know where the actions are kepts so we can register them. [#](#updating-the-paths-to-register-actions) Updating the paths to register actions ------------------------------------------------------------------------------------ By default, Laravel Actions will look into the `app/Actions` folder (or any of its subfolders). You can change that behaviour by calling the `paths()` method from the `Actions` Facade within the `register` method of your `AppServiceProvider`. # Provide your Actions folder. Actions::paths('app/MyActionsFolder'); # You can also provide multiple folders. Actions::paths([\ 'app/Actions', // <- default\ 'app/SomeOtherFolder',\ ]); [#](#disabling-the-registration-of-actions) Disabling the registration of actions ---------------------------------------------------------------------------------- If none of the features listed above are needed for your application, you may disable registering actions entirely via the `dontRegister` method. Actions::dontRegister(); ### [#](#alternative-for-registering-actions-as-commands-automatically) Alternative for "Registering actions as commands automatically" When disabling the registration of actions, you will need to manually register actions as commands. You can do this by calling the `registerCommand` static method in your `routes/console.php` file. PublishANewArticle::registerCommand(); ### [#](#alternative-for-registering-routes-directly-within-actions) Alternative for "Registering routes directly within actions" When disabling the registration of actions, you will not be able to define your routes within the static `routes` method. Instead, you will need to define them in your routes files. See "[Registering actions in routes files](/1.x/actions-as-controllers.html#registering-actions-in-routes-files) ". ← [Actions as commands](/1.x/actions-as-commands.html) [Keeping track of how an action was run](/1.x/action-running-as.html) → --- # Keeping track of how an action was run | Laravel Actions [#](#keeping-track-of-how-an-action-was-run) Keeping track of how an action was run ==================================================================================== [#](#the-runningas-method) The `runningAs` method -------------------------------------------------- In some rare cases, you might want to know how the action is being run. You can access this information using the `runningAs` method. public function handle() { $this->runningAs('object'); $this->runningAs('job'); $this->runningAs('listener'); $this->runningAs('controller'); $this->runningAs('command'); // Returns true if any of them is true. $this->runningAs('object', 'job'); } [#](#the-before-hooks) The before hooks ---------------------------------------- If you want to execute some code only when the action is running as a certain type, you can use the before hooks `asObject`, `asJob`, `asListener`, `asController` and `asCommand`. public function asController(Request $request) { $this->token = $request->cookie('token'); } If you want to prepare the data before running the action (and thus also before validating the data), you can use the `prepareForValidation` method. This method will be run before the `as...` methods, no matter how the action is running as. TIP It is worth noting that, just like the `handle` method, the `prepareForValidation` method and the `as...` methods [support dependency injections](/1.x/dependency-injections.html) . TIP The page "[The lifecycle of an action](/1.x/action-lifecycle.html) " provides a handy summary of all methods that an Action will call before and after executing the `handle` method. WARNING These before hooks will be executed at every run. This means you cannot use the `asController` method to register your middleware. You need to [use the `middleware` method](/1.x/actions-as-controllers.html#registering-middleware) instead. ← [Registering actions](/1.x/registering-actions.html) [Actions within actions](/1.x/nested-actions.html) → --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Actions within actions | Laravel Actions [#](#actions-within-actions) Actions within actions ==================================================== With Laravel Actions you can easily call actions within actions. As you can see in the following example, you can use another action as an object in order to access its result. class CreateNewRestaurant extends Action { public function handle() { $coordinates = FetchGoogleMapsCoordinates::run([\ 'address' => $this->address,\ ]); return Restaurant::create([\ 'name' => $this->name,\ 'longitude' => $coordinates->longitude,\ 'latitude' => $coordinates->latitude,\ ]); } } However, sometimes, you might want to delegate completely to another action. That means the action we delegate to should have the same attributes and run as the same type as the parent action. You can achieve this using the `delegateTo` method. For example, let’s say you have three actions `UpdateProfilePicture`, `UpdatePassword` and `UpdateProfileDetails` that you want to use in a single endpoint. class UpdateProfile extends Action { public function handle() { if ($this->has('avatar')) { return $this->delegateTo(UpdateProfilePicture::class); } if ($this->has('password')) { return $this->delegateTo(UpdatePassword::class); } return $this->delegateTo(UpdateProfileDetails::class); } } In the above example, if we are running the `UpdateProfile` action as a controller, then the sub actions will also be running as controllers. TIP It is worth noting that the `delegateTo` method is implemented using the `createFrom` and `runAs` methods. // These two lines are equivalent. $this->delegateTo(UpdatePassword::class); UpdatePassword::createFrom($this)->runAs($this); ← [Keeping track of how an action was run](/1.x/action-running-as.html) [The lifecycle of an action](/1.x/action-lifecycle.html) → --- # The lifecycle of an action | Laravel Actions [#](#the-lifecycle-of-an-action) The lifecycle of an action ============================================================ ![The lifecycle of an action](/lifecycle.png) [#](#explanations) Explanations -------------------------------- ### [#](#registered) `registered` The static `registered` method is triggered once the Action has been [automatically registered](/1.x/registering-actions.html) . Thus, this method will run at most once per class. ### [#](#initialized) `initialized` This method will be called every time a new action object of this class is constructed. ### [#](#getattributesfrom) `getAttributesFrom...` Overridable methods that maps data from various resources into attributes. * `getAttributesFromEvent` (for listeners) * `getAttributesFromRequest` (for controllers) * `getAttributesFromCommand` (for commands) Note that objects and jobs do not need such methods since their attributes are manually given when created/dispatched. ### [#](#prepareforvalidation) `prepareForValidation` Can be used to provide additional logic before going through the authorisation and validation steps. ### [#](#as) `as...` Similar to the `prepareForValidation` method but different methods will be triggered based on how the actions is running as. * `asObject` * `asJob` * `asListener` * `asController` * `asCommand` ### [#](#authorize-and-failedauthorization) `authorize` and `failedAuthorization` Can be used to provide authorisation logic. See the "[Authorisation](/1.x/authorisation.html) " page. ### [#](#rules-failedvalidation-and-other-validation-methods) `rules`, `failedValidation` and other validation methods Can be used to provide validation logic. See the "[Validation](/1.x/validation.html) " page. ### [#](#handle) `handle` The main method that defines the logic of your action to be executed. This is the only required method. ### [#](#response-controllers-only) `response` (controllers only) Can be used to map the result of the action into a HTTP response. See "[Returning HTTP responses](/1.x/actions-as-controllers.html#returning-http-responses) ". ### [#](#consoleoutput-commands-only) `consoleOutput` (commands only) Can be used to customise what to display to the console after executing the action. See "[Console output and exit code](/1.x/actions-as-commands.html#console-output-and-exit-code) ". ← [Actions within actions](/1.x/nested-actions.html) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) --- # Laravel Actions 404 === > That's a Four-Oh-Four. [Take me home.](/) ---