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.

Responses
200
·
OK
`PostResource`
Body
object
data
PostResource
required
+ Show properties
1
use Illuminate\Http\Resources\Json\JsonResource;
2
3
class PostResource extends JsonResource
4
{
5
public function toArray(): array
6
{
7
return [
8
'id' => $this->id,
9
'title' => $this->title,
10
'body' => $this->body,
11
'author' => UserResource::make($this->whenLoaded('author')),
12
];
13
}
14
}

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 (TodoItemsResourceTodoItem).

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:

1
use App\Domains\Todo\Models\TodoItem;
2
3
/**
4
* @property TodoItem $resource
5
* or
6
* @property-read TodoItem $resource
7
* or
8
* @mixin TodoItem
9
*/
10
class TodoItemResource extends JsonResource
11
{...}

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.

1
return [
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:

1
use App\Http\Resources\ThreadResource;
2
3
return [
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.

Responses
418
·
I'm a teapot
`PostResource`
Body
object
data
PostResource
required
+ Show properties
1
use Illuminate\Http\JsonResponse;
2
3
public function withResponse(Request $request, JsonResponse $response)
4
{
5
$response->setStatusCode(418);
6
}

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.

Responses
200
·
OK
Array of `PostResource`
Body
object
data
PostResource[]
required
+ Show properties
1
use App\Http\Resources\PostResource;
2
use App\Models\Post;
3
4
class PostsController extends Controller
5
{
6
public function index()
7
{
8
return PostResource::collection(Post::all());
9
}
10
}

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.

Responses
200
·
OK
Paginated set of `PostResource`
Body
object
data
PostResource[]
required
+ Show properties
links
object
required
+ Show properties
meta
object
required
+ Show properties
1
use App\Http\Resources\PostResource;
2
use App\Models\Post;
3
4
class PostsController extends Controller
5
{
6
public function index()
7
{
8
return PostResource::collection(Post::paginate());
9
}
10
}

Scramble supports all available paginator classes:

  • Illuminate\Pagination\LengthAwarePaginator (returned when you call paginate method on a query builder),
  • Illuminate\Pagination\Paginator (returned when you call simplePaginate method on a query builder),
  • Illuminate\Pagination\CursorPaginator (returned when you call cursorPaginate method 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.

1
public 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.

1
public 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
}
1
enum JobStatus: string
2
{
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:

1
enum JobStatus: string
2
{
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\BinaryFileResponse
  • Illuminate\Http\Response
  • Illuminate\Http\JsonResponse
  • Symfony\Component\HttpFoundation\StreamedResponse
  • Symfony\Component\HttpFoundation\StreamedJsonResponse

It is common to build instances of these responses using the response() helper function:

Responses
200
·
OK
Body
object
total
integer
required
active
integer
required
managed
integer
required
1
use App\Support\StatsService;
2
3
class StatsController extends Controller
4
{
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.

Responses
400
·
Bad Request
An error
Body
object
message
string
required
Error overview.
Example
This case is not supported
1
class CaseController extends Controller
2
{
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.

1
class UsersController
2
{
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.php
2
class UsersController
3
{
4
public function store(Request $request, UsersService $usersService)
5
{
6
$usersService->createUser($request->email, $request->password);
7
}
8
}
9
10
// UsersService.php
11
class UsersService
12
{
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.

ExceptionStatus code
Illuminate\Auth\AuthenticationException401
Illuminate\Auth\Access\AuthorizationException403
Symfony\Component\HttpKernel\Exception\HttpException$statusCode property value
Illuminate\Database\RecordsNotFoundException404
Symfony\Component\HttpKernel\Exception\NotFoundHttpException404
Illuminate\Validation\ValidationException422

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:

1
use Symfony\Component\HttpKernel\Exception\HttpException;
2
3
// ❌ will not be documented, $statusCode type is `int`
4
public function update(int $statusCode)
5
{
6
throw HttpException($statusCode);
7
}
8
9
// ✅ will be documented properly, the passed type is `409`
10
public 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

New in 0.12.x

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:

1
use Dedoc\Scramble\Attributes\SchemaName;
2
3
#[SchemaName('User')]
4
class UserResource extends JsonResource
5
{...}

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:

  1. PHPDoc comment (@response tag) - gives ability to document arbitrary response types
  2. Typehint - if the method has a typehint and doesn’t have a PHPDoc comment, the typehint will be used
  3. Code return statements - if the method doesn’t have a PHPDoc @response tag, and inferred return type is more specific than typehint, the inferred type will be used
1
use App\Models\TodoItem;
2
3
class TodoItemsController
4
{
5
/**
6
* @response TodoItemResource // 1 - PHPDoc
7
*/
8
public function update(Request $request, TodoItem $item): TodoItemResource // 2 - typehint
9
{
10
return new TodoItemResource($item); // 3 - code inference
11
}
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.

1
public 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.

1
public function create(Request $request)
2
{
3
/**
4
* A user resource.
5
*
6
* @status 201
7
* @body User
8
*/
9
return User::create($request->only(['email']));
10
}

To type anonymous resource collections manually, add the following type:

1
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
2
use App\Models\TodoItem;
3
4
class TodoItemsController
5
{
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:

1
use App\Models\TodoItem;
2
use Illuminate\Pagination\LengthAwarePaginator;
3
4
class TodoItemsController
5
{
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.

Responses
200
·
OK
Headers
X-Retry-After
integer
How long the service is expected to be unavailable to the requesting client
1
use Dedoc\Scramble\Attributes\Header;
2
3
class SampleController
4
{
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:

Responses
200
·
OK
The sample created before
1
use Dedoc\Scramble\Attributes\Response;
2
3
class SampleController
4
{
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:

Responses
200
·
OK
201
·
Created
Created item ID
1
use Dedoc\Scramble\Attributes\Response;
2
3
class SampleController
4
{
5
#[Response(201, 'Created item ID', type: 'array{id: int}')]
6
public function __invoke(Request $request)
7
{/* ... */}
8
}
Scramble PRO
Comprehensive API documentation generation for Spatie’s Laravel Data, Laravel Query Builder, and other packages.