Responses
Introduction
Scramble generates documentation for endpoint responses by analyzing the source code of controller methods. It inspects return statements and calls to methods like validate or authorize to identify the different responses that may be produced.
But you always stay in control: when needed, you can add manual documentation or override the automatically generated one.
Automatic documentation
JSON resources
When returning JSON resources from your routes, Scramble analyzes resource’s toArray return type to document the response. Write your resources as you normally would — using conditional fields, relations, or nested JSON resources — Scramble will automatically generate the documentation.
1use Illuminate\Http\Resources\Json\JsonResource;2
3class PostResource extends JsonResource4{5 public function toArray(): array6 {7 return [8 'id' => $this->id,9 'title' => $this->title,10 'body' => $this->body,11 'author' => UserResource::make($this->whenLoaded('author')),12 ];13 }14}1use App\Http\Resources\PostResource;2use App\Http\Resources\UserResource;3
4class PostsController extends Controller5{6 public function show(Post $post)7 {8 return PostResource::make($post);9 }10}JSON resources are stored in the OpenAPI document as reusable schema components. This means that a resource used in other resources will be rendered as reference. By default, Scramble uses the resource class’ basename as a schema name. You can override it with SchemaName attribute.
Model resolution
To resolve types of properties accessed in a resource (for example, $this->id or $this->resource->name), Scramble needs to know which model corresponds to the resource.
By default, Scramble tries to find a model in App\Models namespace, based on resource name. Before the lookup resource name is converted to singular form (TodoItemsResource → TodoItem).
You can provide the model explicitly by adding PHPDoc to the resource class. You can either document $resource property type (with @property or @property-read annotation), or use the model as a @mixin:
1use App\Domains\Todo\Models\TodoItem;2
3/**4 * @property TodoItem $resource5 * or6 * @property-read TodoItem $resource7 * or8 * @mixin TodoItem9 */10class TodoItemResource extends JsonResource11{...}If the model for the resource is not found, all fields will have string type.
Adding fields documentation
If you need to add more documentation to a field, you can use a PHPDoc annotation. There you can add a description, an example, specify a default value, or redefine the field’s type.
1return [2 'id' => $this->id,3 /**4 * The content of todo item, truncated.5 * @example "I need to get to..."6 * @default "..."7 */8 'content' => $this->getTruncatedContent(),9 /** @format date-time */10 'created_at' => $this->created_at->toDateTimeString(),11];You can use the @var annotation, to redefine the field’s type:
1use App\Http\Resources\ThreadResource;2
3return [4 'id' => $this->id,5 /** @var array<string, ThreadResource> */6 'threads' => $this->threads->keyBy('name')->mapInto(ThreadResource::class),7];This is the format of type annotations used by PHPStan. You can read more about it here: https://phpstan.org/writing-php-code/phpdoc-types.
withResponse method
When you define the withResponse method in your resource, Scramble will analyze it to document the response. This is especially useful when you need to redefine the response status code.
1use Illuminate\Http\JsonResponse;2
3public function withResponse(Request $request, JsonResponse $response)4{5 $response->setStatusCode(418);6}1use App\Http\Resources\PostResource;2use App\Http\Resources\UserResource;3
4class PostsController extends Controller5{6 public function show(Post $post)7 {8 return PostResource::make($post);9 }10}Additional resource’s top-level data
Scramble will document additional resource’s top-level data that is defined in with resource’s method, or passed to additional method on a resource instance. Additional fields can be manually annotated in the same way as other resource’s fields.
Data wrapping
When documenting the JSON resource response, Scramble takes your data wrapping setup into account: this includes determining the wrap key or its absence, and properly handing the case when the data is already wrapped manually.
Resource collections
Scramble supports automatic documentation of both anonymous resource collections (created by calling collection method of a resource) and custom resource collections.
1use App\Http\Resources\PostResource;2use App\Models\Post;3
4class PostsController extends Controller5{6 public function index()7 {8 return PostResource::collection(Post::all());9 }10}1use Illuminate\Http\Resources\Json\JsonResource;2
3class PostResource extends JsonResource4{5 public function toArray(): array6 {7 return [8 'id' => $this->id,9 'title' => $this->title,10 'body' => $this->body,11 'author' => UserResource::make($this->whenLoaded('author')),12 ];13 }14}Because anonymous and custom resource collections classes extend base JSON resource class, all available features of documenting JSON resources work for resource collections too: withResponse, with, additional, etc.
Pagination
When you pass a paginator instance to collection method of a resource or to a custom resource collection, Scramble will document the paginated response.
1use App\Http\Resources\PostResource;2use App\Models\Post;3
4class PostsController extends Controller5{6 public function index()7 {8 return PostResource::collection(Post::paginate());9 }10}1use Illuminate\Http\Resources\Json\JsonResource;2
3class PostResource extends JsonResource4{5 public function toArray(): array6 {7 return [8 'id' => $this->id,9 'title' => $this->title,10 'body' => $this->body,11 'author' => UserResource::make($this->whenLoaded('author')),12 ];13 }14}Scramble supports all available paginator classes:
Illuminate\Pagination\LengthAwarePaginator(returned when you callpaginatemethod on a query builder),Illuminate\Pagination\Paginator(returned when you callsimplePaginatemethod on a query builder),Illuminate\Pagination\CursorPaginator(returned when you callcursorPaginatemethod on a query builder).
Customizing the pagination information
You can define paginationInformation method on your resource collection class to customize the pagination information. Scramble will document this too.
1public function paginationInformation($request, $paginated, $default)2{3 $default['links']['custom'] = 'https://example.com';4
5 return $default;6}Laravel Data objects
Scramble has first class Laravel Data support: singular data objects, data collections, include/exclude, only/except, wrapping, etc. Learn more about Laravel Data support.
Models
When you return models from controller methods or reference them in other response structures (such as JSON resources), Scramble documents them automatically.
Scramble analyzes both visible and hidden attributes to determine the model’s schema. If the toArray method is overridden, that is also taken into account.
To determine an attribute’s type, Scramble first checks the model’s casts. If the type is not defined there, it inspects the model’s database table. So for Scramble to analyze attribute types correctly, all migrations must be applied.
Models are documented as reusable schema components. Because of this, only relationships that are always loaded (those referenced in the $with property) are included in the model’s schema.
1public function show(Request $request, User $user)2{3 return $user;4}Enums
Scramble documents backed enums using the enum JSON schema property:
1{2 "title": "JobStatus",3 "type": "string",4 "enum": ["open", "closed"]5}1enum JobStatus: string2{3 case OPEN = 'open';4 case CLOSED = 'closed';5}You can add a description to an enum case by adding a PHPDoc comment to it:
1enum JobStatus: string2{3 /**4 * When the job application is available.5 */6 case OPEN = 'open';7
8 /**9 * When the job has been closed.10 */11 case CLOSED = 'closed';12}The JSON Schema specification does not currently have an official way to describe enum cases. So by default, Scramble includes case descriptions as a Markdown table in the enum schema’s description:
1{2 "title": "JobStatus",3 "type": "string",4 "enum": ["open", "closed"],5 "description": "|---|---|\n|`open`|When the job application is available.|\n..."6}Alternatively, you can store case descriptions using the x-enumDescriptions extension by setting scramble.enum_cases_description_strategy to extension. The x-enumDescriptions extension is supported by some OpenAPI documentation renderers (such as Redocly):
1{2 "title": "JobStatus",3 "type": "string",4 "enum": ["open", "closed"],5 "x-enumDescriptions": {6 "open": "When the job application is available.",7 "closed": "When the job has been closed."8 }9}HTTP responses
When one of the following HTTP responses instances returned from controller methods, Scramble will document them:
Symfony\Component\HttpFoundation\BinaryFileResponseIlluminate\Http\ResponseIlluminate\Http\JsonResponseSymfony\Component\HttpFoundation\StreamedResponseSymfony\Component\HttpFoundation\StreamedJsonResponse
It is common to build instances of these responses using the response() helper function:
1use App\Support\StatsService;2
3class StatsController extends Controller4{5 public function __invoke(StatsService $stats)6 {7 return response()->json([8 'total' => $stats->getTotal(),9 'active' => $stats->getActive(),10 'managed' => $stats->getManaged(),11 ]);12 }13}Error responses
By analyzing the codebase Scramble can understand not only OK responses, but error responses as well. Here are the cases which Scramble understands and can document.
Call to validate and authorize in controller
Calls to validate and authorize will be documented as corresponding error responses in resulting docs: responses with 422 and 403 statuses respectively.
Model binding in route
When model binding is used, Scramble adds possible 404 error response to the documentation. This is the case when a model is not found.
Abort helpers
When using abort helpers (abort, abort_if, abort_unless), Scramble will document the resulting response.
Currently only numeric code is supported. Message content (message) will be documented as an example in resulting docs.
1class CaseController extends Controller2{3 public function __invoke()4 {5 abort(400, 'This case is not supported');6 }7}Exceptions
Under the hood Scramble understands that abort* helpers, *::validate, *::authorize methods throw exceptions. Then, these exceptions are documented as error responses.
If you add @throws annotation with a supported exception to your route’s controller method, Scramble will document it as the error response.
1class UsersController2{3 /** @throws \Illuminate\Auth\Access\AuthorizationException */4 public function store(Request $request, UsersService $usersService)5 {6 $usersService->createUser($request->email, $request->password);7 }8}Scramble also does one level deep lookup into called methods’ PHPDoc annotations for exceptions (annotated with @throws annotation). So in case createUser method from the previous example has @throws annotation in place, Scramble will document 403 response without any annotations on controller’s method:
1// app/Http/Controllers/API/UsersController.php2class UsersController3{4 public function store(Request $request, UsersService $usersService)5 {6 $usersService->createUser($request->email, $request->password);7 }8}9
10// UsersService.php11class UsersService12{13 /** @throws \Illuminate\Auth\Access\AuthorizationException */14 public function createUser(string $email, string $password)15 {...}16}Here are the exceptions that will be automatically documented by Scramble as a response. All the exceptions that extend the following exceptions classes will also be documented as the same responses.
| Exception | Status code |
|---|---|
Illuminate\Auth\AuthenticationException | 401 |
Illuminate\Auth\Access\AuthorizationException | 403 |
Symfony\Component\HttpKernel\Exception\HttpException | $statusCode property value |
Illuminate\Database\RecordsNotFoundException | 404 |
Symfony\Component\HttpKernel\Exception\NotFoundHttpException | 404 |
Illuminate\Validation\ValidationException | 422 |
Any instance of Symfony\Component\HttpKernel\Exception\HttpException will be documented as the error response with the status code in $statusCode property. But it is important to keep in mind, that the status code type must be literal integer so Scramble is able to “get” the value:
1use Symfony\Component\HttpKernel\Exception\HttpException;2
3// ❌ will not be documented, $statusCode type is `int`4public function update(int $statusCode)5{6 throw HttpException($statusCode);7}8
9// ✅ will be documented properly, the passed type is `409`10public function update()11{12 throw HttpException(409);13}In case you have custom exception, you can define how the exception is represented as a response using ExceptionToResponseExtension.
Here are some examples of this kind of extensions that add support for already mentioned exceptions:
Explicitly naming schemas
Eloquent models, JSON API resources, and enums are represented as named schemas in the documentation. A schema is named using the corresponding class name. You can name the schema explicitly by using #[SchemaName] attribute on the class:
1use Dedoc\Scramble\Attributes\SchemaName;2
3#[SchemaName('User')]4class UserResource extends JsonResource5{...}This is particularly useful when you have the classes with the same names but in a different namespaces. By default, Scramble will use the fully qualified class name for a classes with the same name to guarantee uniqueness. This may not be the ideal for you, so you can use SchemaName and provide the name explicitly.
Manual documentation
When you need to, you can add documentation manually, or you can change the documentation inferred by static code analysis.
Understanding response type priority
Scramble extracts controller’s method return type based on the following priority:
- PHPDoc comment (
@responsetag) - gives ability to document arbitrary response types - Typehint - if the method has a typehint and doesn’t have a PHPDoc comment, the typehint will be used
- Code return statements - if the method doesn’t have a PHPDoc
@responsetag, and inferred return type is more specific than typehint, the inferred type will be used
1use App\Models\TodoItem;2
3class TodoItemsController4{5 /**6 * @response TodoItemResource // 1 - PHPDoc7 */8 public function update(Request $request, TodoItem $item): TodoItemResource // 2 - typehint9 {10 return new TodoItemResource($item); // 3 - code inference11 }12}This is implemented in this way so if needed, you can override the inferred type with a typehint or PHPDoc comment.
Modifying inferred documentation
You can add the response description by adding a comment right on top of the corresponding return statement in a controller.
1public function show(Request $request, User $user)2{3 // A user resource.4 return new UserResource($user);5}Response status and type can be added manually as well using @status and @body tags in PHPDoc block on top of the corresponding return statement.
1public function create(Request $request)2{3 /**4 * A user resource.5 *6 * @status 2017 * @body User8 */9 return User::create($request->only(['email']));10}To type anonymous resource collections manually, add the following type:
1use Illuminate\Http\Resources\Json\AnonymousResourceCollection;2use App\Models\TodoItem;3
4class TodoItemsController5{6 /**7 * List available todo items.8 *9 * @response AnonymousResourceCollection<TodoItemResource>10 */11 public function index(Request $request)12 {13 return TodoItemResource::collection(TodoItem::all());14 }15}To type the paginator instance with the model, provide the following type:
1use App\Models\TodoItem;2use Illuminate\Pagination\LengthAwarePaginator;3
4class TodoItemsController5{6 /**7 * @response LengthAwarePaginator<int, TodoItem>8 */9 public function index(Request $request)10 {11 return TodoItem::paginate();12 }13}Headers
To add headers documentation manually, use Header attribute.
1use Dedoc\Scramble\Attributes\Header;2
3class SampleController4{5 #[Header('X-Retry-After', 'How long the service is expected to be unavailable to the requesting client', type: 'int')]6 public function __invoke(Request $request)7 {/* ... */}8}Responses
To add responses documentation manually, use Response attribute.
Unless you specify status code and media type, provided response documentation will be “attached” to the response with status code 200 and media type application/json:
1use Dedoc\Scramble\Attributes\Response;2
3class SampleController4{5 #[Response('The sample created before')]6 public function __invoke(Request $request)7 {/* ... */}8}You can add more responses to the documentation on top of those responses inferred from the source code by providing status code and media type:
1use Dedoc\Scramble\Attributes\Response;2
3class SampleController4{5 #[Response(201, 'Created item ID', type: 'array{id: int}')]6 public function __invoke(Request $request)7 {/* ... */}8}