scramble-laravel-actions maintained by rayronvictor
Scramble support for Laravel Actions
Document the actions you use as controllers with Laravel Actions in the OpenAPI documentation generated by Scramble.
Laravel Actions registers an action as an invokable controller (CreateTodo@__invoke) and only decides at request time which method actually runs, how the request is validated and what the response looks like. Scramble never runs your routes, so out of the box it documents the generic __invoke method from the AsController trait: no request body, no validation errors and no response.
This package teaches Scramble the Laravel Actions conventions.
Installation
composer require rayronvictor/scramble-laravel-actions
The service provider is registered automatically through package discovery. There is nothing else to configure.
Requirements
- PHP 8.2+
- Laravel 11, 12 or 13
dedoc/scramble^0.13.44lorisleiva/laravel-actions^2.8
What gets documented
Given this action:
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
/**
* Create a todo.
*
* Stores a new todo for the current user.
*/
class CreateTodo
{
use AsAction;
public function getControllerMiddleware(): array
{
return ['auth:sanctum'];
}
public function authorize(ActionRequest $request): bool
{
return $request->user()->can('create', Todo::class);
}
public function rules(): array
{
return [
// The title of the todo.
'title' => ['required', 'string', 'max:255'],
'done' => ['sometimes', 'boolean'],
];
}
public function handle(User $user, string $title, bool $done = false): Todo
{
return $user->todos()->create(compact('title', 'done'));
}
public function asController(ActionRequest $request): Todo
{
return $this->handle($request->user(), $request->validated('title'), $request->boolean('done'));
}
public function jsonResponse(Todo $todo): TodoResource
{
return new TodoResource($todo);
}
}
Route::post('todos', CreateTodo::class);
Scramble now documents:
| Laravel Actions convention | Documentation |
|---|---|
asController (or handle when there is no asController) |
Analyzed as the route method: path parameters, route model binding, request calls like $request->input('q') and the return type. |
rules() |
Request body (or query parameters for GET/DELETE), as a CreateTodoRequest schema. Comments on the rules become descriptions, just like with a FormRequest. |
rules(), withValidator(), afterValidator(), getValidator() |
422 validation error response. |
authorize() |
403 authorization error response, unless it always returns true. |
getControllerMiddleware() |
Middlewares like auth add the 401 response, as with regular controllers. |
jsonResponse($result, $request) |
Response of the operation. The type returned by the route method is passed to it, so jsonResponse($todo) { return response()->json(['data' => $todo]); } is documented with the right data shape. |
| Class PHPDoc | Summary and description of the operation when the route method has none. Tags from the class PHPDoc (like @response or @unauthenticated) apply too, while the route method ones take precedence. |
| Operation ID | Ends with the action name (…createTodo) instead of …createTodo.handle, like Scramble does for __invoke. |
Everything else keeps working as Scramble documents it: attributes like #[Group], #[ExcludeRouteFromDocs], #[Api] or #[Response], PHPDoc annotations, resources, models, etc. Put them on the route method (asController or handle) or on the class.
Routes pointing to an explicit method of an action, like Route::get('todos/export', [ExportTodos::class, 'export']), are documented like any other controller method, because Laravel Actions does not validate the request nor transform the response for them.
How rules are evaluated
rules() is called the same way Laravel Actions calls it: the action is resolved from the container and the dependencies of rules() are injected, with an ActionRequest whose HTTP method matches the route. When that fails (for example because the rules need a route parameter or the authenticated user), Scramble's static evaluation of the returned array is used instead, the same fallback it uses for form requests.
Multiple APIs
The package registers its extensions on Scramble's default API configuration, which every API registered afterwards with Scramble::registerApi() inherits. If you register an API before this package's service provider boots, configure it yourself:
use RayronVictor\ScrambleLaravelActions\ScrambleLaravelActions;
ScrambleLaravelActions::configure(Scramble::registerApi('v2', ['api_path' => 'api/v2']));
How it works
ActionRouteProviderdecorates Scramble's route provider and, on a copy of each action route, swaps__invokefor the method Laravel Actions would call (asController, thenhandle). Your application routes are never modified.ActionRulesParametersExtractordocuments the request from the actionrules(). Scramble's form request extractor ignoresActionRequest, whoserules()only proxies the action's.PrepareActionOperationExtensionruns before Scramble's operation extensions: it falls back to the class PHPDoc and attaches the validation and authorization errors of the action.ActionResponseExtensionruns after them: it documents thejsonResponse()result and cleans up the operation ID.
Testing
composer test
License
The MIT License (MIT). See LICENSE for more information.