API resources
- Resource collection
- Concept overview
- Key to protect the collection
- Customized basic resources Class
- Wrapping nested resources
- Data wrapping and paging
- ##Paging
- Conditional attributes
- Conditional merged data
- Conditional intermediate table information
- Top-level metadata
- Add metadata when constructing resources
- Response resources
Eloquent: API resources
##Introductionmake:resource
Artisan command to generate a resource class. By default, the generated resources will be placed in the application's
app/Http/Resourcesfolder. The resource inherits from
Illuminate\Http\Resources\Json\JsonResourceClass:
php artisan make:resource User
Resource CollectionIn addition to generating a single model for resource conversion, you can also generate a resource collection for to transform a collection of models. This allows you to include links and other meta-information related to a given resource in the response. You need to add the--collection
flag when generating resources to generate a resource collection. Alternatively, you can directly include
Collectionin the name of the resource to indicate to Laravel that a collection of resources should be generated. Resource collection inherits from
Illuminate\Http\Resources\Json\ResourceCollectionClass:
php artisan make:resource Users --collection php artisan make:resource UserCollection
{tip} This is a high-level overview of resources and resource collections. It is highly recommended that you read other sections of this document to gain insight into how to better customize and use the resource.Before we dive into how to custom write your resources, let’s first take a look at how to use resources in Laravel. A resource class represents a single model that needs to be converted into JSON format. For example, now we have a simple
Userresource class:
toArray
Each resource class defines a<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class User extends JsonResource{ /** * 将资源转换成数组。 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } }
method, which will return the should when sending the response An array of properties converted to JSON. Note that here we can directly use the
$this
variable to access the model properties. This is because the resource class will automatically proxy properties and methods to the underlying model for easy access. You can return defined resources in routes or controllers:
Resource Collectionuse App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return new UserResource(User::find(1)); });
You can use the
collectionmethod in routes or controllers to create resource instances , to return a collection of multiple resources or a paginated response:
use App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return UserResource::collection(User::all()); });
Of course, using the above method you will not be able to add any additional metadata and return it with the collection. If you need a custom resource collection response, you need to create a dedicated resource to represent the collection:
php artisan make:resource UserCollection
You can easily define any metadata you want to return in the response in the generated resource collection class :
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * 将资源集合转换成数组。 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; } }
You can return a defined resource collection in a route or controller:
use App\User; use App\Http\Resources\UserCollection; Route::get('/users', function () { return new UserCollection(User::all()); });
Protect the collection's key
When returning a resource collection from a route, Laravel will Resets the collection's keys so that they are in simple numerical order. However, the
preserveKeys
attribute can be added to the resource class to indicate whether the collection keys should be preserved:<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class User extends JsonResource{ /** * 指示是否应保留资源的集合键。 * * @var bool */ public $preserveKeys = true; }
When the
preserveKeys
attribute is set totrue
, The key of the collection will be protected:use App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return UserResource::collection(User::all()->keyBy->id); });
Customized basic resource class
Normally, the
$this->collection
attribute of the resource collection will be automatically populated, The result is that each item of the collection is mapped to its individual resource class. A single resource class is assumed to be the class name of a collection, but without theCollection
string at the end.For example,
UserCollection
maps a given user instance to aUser
resource. To customize this behavior, you can override the$collects
property of the resource collection:<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * collects 属性定义了资源类。 * * @var string */ public $collects = 'App\Http\Resources\Member'; }
Writing Resources
{tip} If you have not read Concept Overview, it is strongly recommended that you read it before continuing to read this document.
In essence, the role of resources is simple. They just need to convert a given model into an array. So each resource contains a
toArray
method that converts your model properties into an API-friendly array that can be returned to the user:<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class User extends JsonResource{ /** * 将资源转换成数组。 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } }
You can return it in a route or controller Already defined resources:
use App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return new UserResource(User::find(1)); });
Associations
If you wish to include associated resources in the response, you only need to add them to the array returned by the
toArray
method. In the following example, we will use thecollection
method of thePost
resource to add the user's posts to the resource response:/** * 将资源转换成数组。 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts' => PostResource::collection($this->posts), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }
{tip} If You only want to add associated resources when the association is already loaded, see the documentation for conditional associations.
Resource collection
Resources convert a single model into an array, while resource collections convert a collection of multiple models into an array. All resources provide a
collection
method to generate a "temporary" resource collection, so you do not need to write a resource collection class for each model type:use App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return UserResource::collection(User::all()); });
However, if If you need to customize the metadata of the returned collection, you still need to define a resource collection:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * 将资源集合转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; } }
Like a single resource, you can directly return a resource collection in a route or controller:
use App\User; use App\Http\Resources\UserCollection; Route::get('/users', function () { return new UserCollection(User::all()); });
Data Wrapping
By default, when the resource response is converted to JSON, the top-level resource will be wrapped in the
data
key. So a typical resource collection response looks like this:{ "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", "email": "therese28@example.com", }, { "id": 2, "name": "Liliana Mayert", "email": "evandervort@example.com", } ] }
You can disable wrapping of top-level resources using the
withoutWrapping
method of the resource base class. Typically, you should call this method inAppServiceProvider
or other service provider that will be loaded on every request of the program:<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Http\Resources\Json\Resource; class AppServiceProvider extends ServiceProvider{ /** * 在注册后进行服务的启动 * * @return void */ public function boot() { Resource::withoutWrapping(); } /** * 在容器中注册绑定 * * @return void */ public function register() { // } }
{note}
withoutWrappin
This method will only disable the wrapping of the top-level resource and will not delete thedata
key you manually added to the resource collection.Wrapping nested resources
You have complete freedom in deciding how resource associations are wrapped. If you want all resource collections to be wrapped in
data
keys no matter how nested they are, then you need to define a resource collection class for each resource and wrap the returned collection indata
key.Of course, you may worry that the top-level resource will be wrapped in two
data
keys. Rest assured, Laravel will never let your resources be double wrapped, so you don't have to worry about the converted collection of resources being multi-nested:<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class CommentsCollection extends ResourceCollection{ /** * 将资源集合转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return ['data' => $this->collection]; } }
Data Wrapping and Pagination
When When returning a paginated collection in a resource response, Laravel will wrap your resource data in the
data
key, even if you call thewithoutWrapping
method. This is because there are alwaysmeta
andlinks
keys in the paginated response that contain pagination status information:{ "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", "email": "therese28@example.com", }, { "id": 2, "name": "Liliana Mayert", "email": "evandervort@example.com", } ], "links":{ "first": "http://example.com/pagination?page=1", "last": "http://example.com/pagination?page=1", "prev": null, "next": null }, "meta":{ "current_page": 1, "from": 1, "last_page": 1, "path": "http://example.com/pagination", "per_page": 15, "to": 10, "total": 10 } }
Pagination
You can pass a pagination instance to the resource's
collection
method or a custom resource collection:use App\User; use App\Http\Resources\UserCollection; Route::get('/users', function () { return new UserCollection(User::paginate()); });
There is always
meta
in the pagination response. andlinks
keys contain paging status information:{ "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", "email": "therese28@example.com", }, { "id": 2, "name": "Liliana Mayert", "email": "evandervort@example.com", } ], "links":{ "first": "http://example.com/pagination?page=1", "last": "http://example.com/pagination?page=1", "prev": null, "next": null }, "meta":{ "current_page": 1, "from": 1, "last_page": 1, "path": "http://example.com/pagination", "per_page": 15, "to": 10, "total": 10 } }
Conditional attributes
Sometimes, you may want to give Add attributes to the resource response when certain conditions are met. For example, you might want to add a value to the resource response if the current user is an "admin". In this case Laravel provides some helper methods to help you solve the problem. The
when
method can be used to conditionally add attributes to the resource response:/** * 将资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'secret' => $this->when(Auth::user()->isAdmin(), 'secret-value'), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }
In the above example, only if the
isAdmin
method returnstrue
, thesecret
key will finally be returned in the resource response. If this method returnsfalse
, thesecret
key will be deleted before the resource response is sent to the client. Thewhen
method allows you to avoid using conditional statements to concatenate arrays and instead write your resources in a more elegant way. Thewhen
method also accepts a closure as its second parameter, and the returned value is calculated from the closure only if the given condition istrue
:'secret' => $this->when(Auth::user()->isAdmin(), function () { return 'secret-value'; }),
Conditionally merging data
Sometimes, you may want to add multiple attributes to the resource response when a given condition is met. In this case, you can use the
mergeWhen
method to add multiple attributes to the response when the given condition istrue
:/** * 将资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, $this->mergeWhen(Auth::user()->isAdmin(), [ 'first-secret' => 'value', 'second-secret' => 'value', ]), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }
Similarly, If the given condition is
false
, these attributes will be removed before the resource response is sent to the client.{note}
mergeWhen
method should not be used in arrays with mixed string and numeric keys. Additionally, it should not be used in arrays of non-sequential numeric keys.Conditional Association
In addition to conditionally adding properties, you can also conditionally add properties based on whether the model association has been loaded. Include the association in your resource response. This allows you to decide in the controller which model associations are loaded, so that your resources can only add them after the model associations are loaded.
Doing this will avoid "N 1" query problems in your resources. You should use the
whenLoaded
method to conditionally load associations. To avoid loading unnecessary associations, this method accepts the name of the association as its parameter instead of the association itself:/** * 将资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts' => PostResource::collection($this->whenLoaded('posts')), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }
In the above example, if the association is not loaded, the
posts
key Will be deleted before the resource response is sent to the client.Conditional intermediate table information
In addition to conditionally including associations in your resource response, you can also use the
whenPivotLoaded
method to conditionally join from many-to-many Add data to the intermediate table.whenPivotLoaded
The first parameter accepted by the method is the name of the intermediate table. The second parameter is a closure that defines the value to be returned if the intermediate table information is available on the model:/** * 将资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'id' => $this->id, 'name' => $this->name, 'expires_at' => $this->whenPivotLoaded('role_user', function () { return $this->pivot->expires_at; }), ]; }
If your intermediate table uses something other than
pivot
accessor, you can use thewhenPivotLoadedAs
method:/** * 将资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'id' => $this->id, 'name' => $this->name, 'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () { return $this->subscription->expires_at; }), ]; }
Add metadata
Some JSON API standards require you to Add metadata to resource and resource collection responses. This usually includes
links
for the resource or related resources, or some metadata about the resource itself. If you need to return additional metadata about the resource, just include them in thetoArray
method. For example when converting a collection of resources you might want to addlinks
information:/** * 将资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request){ return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; }
When adding additional metadata to your resources, you don't have to worry about overwriting Laravel when returning paginated responses. Automatically added
links
ormeta
keys. Any otherlinks
you add will be merged with thelinks
added by the paginated response.Top-level metadata
Sometimes you may want to add some metadata to the resource response when the resource is returned as a top-level resource. This usually includes meta-information about the entire response. You can add the
with
method to the resource class to define metadata. This method should return an array of metadata that will be included in the resource response when the resource is rendered as a top-level resource:<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * 将资源集合转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return parent::toArray($request); } /** * 返回应该和资源一起返回的其他数据数组 * * @param \Illuminate\Http\Request $request * @return array */ public function with($request) { return [ 'meta' => [ 'key' => 'value', ], ]; } }
Add metadata when constructing the resource
You also Top-level data can be added when constructing resource instances in routes or controllers. All resources can use the
additional
method to accept an array of data that should be added to the resource response:return (new UserCollection(User::all()->load('roles'))) ->additional(['meta' => [ 'key' => 'value', ] ]);
Response Resources
As you know, resources can be returned directly in routes and controllers:
use App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return new UserResource(User::find(1)); });
But sometimes, you may need to customize the HTTP response before sending it to the client. You have two options. First, you can call the
response
method on the chain. This method will return anIlluminate\Http\Response
instance, allowing you to customize the response header information:use App\User; use App\Http\Resources\User as UserResource; Route::get('/user', function () { return (new UserResource(User::find(1))) ->response() ->header('X-Value', 'True'); });
In addition, you can also define a
withResponse
in the resource method. This method will be called when the resource is used as a top-level resource in response:<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class User extends JsonResource{ /** * 资源转换成数组 * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, ]; } /** * 自定义响应 * * @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Response $response * @return void */ public function withResponse($request, $response) { $response->header('X-Value', 'True'); } }
This article was first published on the LearnKu.com website.