API resource class: bridge between the model and JSON API


brief introduction

When building the API, a conversion layer may be required between the Eloquent model and the JSON response finally returned to the application user. Larave's resource class allows you to convert models and model collections into JSON format data in a simple and elegant way.

Generate resource class

To generate an asset class, you can use the Artisan command make:resource , by default, resource classes are stored in the app/Http/Resources Directory, resource classes are inherited from Illuminate\Http\Resources\Json\Resource Base class:

 php artisan make:resource UserResource

Resource Collection

In addition to generating resource classes that transform independent models, you can also generate resource classes that transform model collections. In this way, the response can contain links and other meta information related to the entire set of resources.

To create a resource collection processing class, you need to use the --collection Tag, or include words in the resource name Collection To tell Laravel that a resource collection class needs to be created. The resource collection class inherits from Illuminate\Http\Resources\Json\ResourceCollection Base class:

 php artisan make:resource Users --collection php artisan make:resource UserCollection

Core concepts

Note: This is a high-level overview of resources and resource collections. It is strongly recommended that you read other parts of this document to further enhance your understanding of the functions and customization provided by resource classes.

Before going deep into all the functions provided by resource classes, let's take a high-level look at how to use resource classes in Larave. A resource class represents a separate model that needs to be converted into a JSON data structure. For example, the following is a simple UserResource Class:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\Resource; class UserResource extends Resource { /** * Transform the resource into an array. * * @param  \Illuminate\Http\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, ]; } }

Each resource class contains a toArray Method is used to return the attribute array that needs to be converted into JSON when sending the response. Note that we can use the $this Variables directly access model properties, because the resource class is a proxy that can access the properties and methods provided by the underlying corresponding model. After the resource class is defined, you can return from the route or controller:

 use App\User; use App\Http\Resources\UserResource; Route::get('/user', function () { return new UserResource(User::find(1)); });

Resource Collection

If you need to return a resource collection or paging response, you can use it when creating a resource instance in the route or controller collection method:

 use App\User; use App\Http\Resources\UserResource; Route::get('/user', function () { return UserResource::collection(User::all()); });

Of course, this method cannot add metadata other than model data that needs to be returned together with the collection. If you want to customize the resource collection response, you can create a special resource class to represent the collection:

 php artisan make:resource UserCollection

After the resource collection class is generated, you can easily define the metadata that needs to be included in the response:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection { /** * Transform the resource collection into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; } }

After defining the resource collection class, you can return it from the route or controller:

 use App\User; use App\Http\Resources\UserCollection; Route::get('/users', function () { return new UserCollection(User::all()); });

Write resource class

Note: If you haven't read Core concepts It is strongly recommended that you read that part of the document before continuing.

In fact, resource classes are very simple. All they do is convert the given model into an array. Therefore, each resource class contains toArray Method is used to convert model attributes into an API friendly array that can be returned to the user:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\Resource; class UserResource extends Resource { /** * Transform the resource into an array. * * @param  \Illuminate\Http\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, ]; } }

After defining the resource class, you can directly return it from the route or controller:

 use App\User; use App\Http\Resources\UserResource; Route::get('/user', function () { return new UserResource(User::find(1)); });

Association

If you want to include associated resources in the response, you can add them to toArray Method. In this example, we use Post Resource class collection Method: Add user blog posts to resource response:

 /** *Convert resources to arrays * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts' => Post::collection($this->posts), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }
Note: If you want to include association relationships only after they are loaded, please refer to the association relationship part of the attached conditions.

Resource Collection

The resource class is used to convert a single model into an array, while the resource collection class is used to convert the model collection into an array. Not every type of model needs to define a resource collection class, because all resource classes provide a collection Method immediately generates a specific resource set:

 use App\User; use App\Http\Resources\UserResource; Route::get('/user', function () { return UserResource::collection(User::all()); });

However, if you need to customize the metadata returned with the collection, you need to define a resource collection class:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection { /** * Transform the resource collection into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; } }

Like a single resource class, a resource collection class can also return directly from a route or controller:

 use App\User; use App\Http\Resources\UserCollection; Route::get('/users', function () { return new UserCollection(User::all()); });

Data packaging

By default, the outermost resources will be wrapped in a data For example, the response data of a typical resource set is as follows:

 { "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", "email": " therese28@example.com ", }, { "id": 2, "name": "Liliana Mayert", "email": " evandervort@example.com ", } ] }

If you want to prohibit wrapping the outermost resource, you can call the withoutWrapping Method, usually, you need to AppServiceProvider Or call this method from the service provider that every other request will load:

 <? php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Http\Resources\Json\Resource; class AppServiceProvider extends ServiceProvider { /** * Perform post-registration booting of services. * * @return void */ public function boot() { Resource::withoutWrapping(); } /** * Register bindings in the container. * * @return void */ public function register() { // } }
Note: withoutWrapping It only affects the outermost resources, and does not remove the data Key.

Wrapping nested resources

You can decide how to package the association of resources. If you want to gather all the resources and package them to data Key, regardless of the nesting between them, you need to define a resource collection class for each resource and use the data Key to return this collection.

Of course, you may worry about whether this will result in the outermost resource being packaged into two data Key. If you think like this, you are completely over worried. Larave will never allow resources to be double wrapped, so you don't need to worry about nesting of transformed resource sets:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class CommentsCollection extends ResourceCollection { /** * Transform the resource collection into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return ['data' => $this->collection]; } }

Data Packaging and Paging

When the paging collection is returned in the resource response, Larave will wrap the resource data into data Key, even if the withoutWrapping Methods are no exception. This is because paging responses always contain meta and links Key:

 { "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 } }

paging

You may often need to pass the pager instance to the resource class collection Method or user-defined resource collection class:

 use App\User; use App\Http\Resources\UserCollection; Route::get('/users', function () { return new UserCollection(User::paginate()); });

The paging response always contains a meta and links Key:

 { "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 attribute

Sometimes you may want to include a property in the resource response only if a given condition is met. For example, you may want to include a value only if the current user is an administrator. To this end, Laravel provides several auxiliary functions to help you achieve this function, when Method can be used to add attributes to the resource response if certain conditions are met:

 /** *Convert resources to arrays * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'secret' => $this->when($this->isAdmin(), 'secret-value'), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }

In this example, secret The key can only be used $this->isAdmin() Method return true In the final resource response. If this method returns false secret The key will be completely removed from the returned data before the resource response is sent to the client. when Method allows you to define resource classes gracefully at any time without having to rewrite conditional statements when building arrays.

when Method can also accept a closure as the second parameter, which allows you to true Calculate the return value when:

 'secret' => $this->when($this->isAdmin(), function () { return 'secret-value'; }),
Note: Remember that method calls on resource classes actually proxy underlying model instance methods, so in this example, isAdmin The actual underlying layer of method invocation User Methods on the model.
Merge attributes with conditions

Sometimes you may have multiple attributes included in the resource response based on the same condition. In this case, you can use mergeWhen Method under the given condition of true The following attributes are included in the response:

 /** * Transform the resource into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, $this->mergeWhen($this->isAdmin(), [ 'first-secret' => 'value', 'second-secret' => 'value', ]), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }

Again, if the given condition is false , these attributes will be completely removed from the response data before the resource response is sent to the client.

Note: mergeWhen Method cannot be used in an array of mixed strings and numeric keys. In addition, it cannot be used in an array of pure numeric keys without sequential sorting.

Conditional association

In addition to loading attributes through conditions, you can also include association relationships in resource responses based on whether the given association relationship is loaded on the model. In this way, the controller can decide which associations need to be loaded, and then the resource class can easily include them when the associations have indeed been loaded.

Finally, this mechanism also makes it easy for us to avoid the N+1 query problem in the resource class. whenLoaded Method can be used for loading association relationships with conditions. To avoid unnecessary association loading, this method receives the name of the association rather than the association itself:

 /** * Transform the resource into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts' => Post::collection($this->whenLoaded('posts')), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }

In this example, if the association has not been loaded, posts The key is removed from the response data before the resource response is sent to the client.

Intermediate table information with conditions

In addition to conditionally containing association information in the resource response, you can also use whenPivotLoaded Method To import qualified data from the middle table of many to many association relationship. whenPivotLoaded The method receives the name of the intermediate table as the first parameter, and the second parameter is a closure, which defines the data returned if the model corresponding to the intermediate table information is valid:

 /** * Transform the resource into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'expires_at' => $this->whenPivotLoaded('role_users', function () { return $this->pivot->expires_at; }), ]; }

Add Metadata

Some JSON API standards require that additional metadata be added to the resource response or resource collection response. These metadata usually include links , or metadata about the resource itself. If you need to return additional metadata about resources, you can toArray Method. For example, you can introduce links Information:

 /** * Transform the resource into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; }

In the scenario of returning additional metadata from resource classes, you never have to worry about accidentally overwriting the links or meta Key, any custom extra links Keys are merged into the links provided by the pager.

Top Metadata

Sometimes you may want to include specific metadata only if the resource is the outermost returned data. Normally, this will contain metadata about the entire response. To define this metadata, you need to add a with Method to your resource class. This method only returns a metadata array included in the resource response when the resource is the outermost rendered data:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection { /** * Transform the resource collection into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return parent::toArray($request); } /** * Get additional data that should be returned with the resource array. * * @param \Illuminate\Http\Request  $request * @return array */ public function with($request) { return [ 'meta' => [ 'key' => 'value', ], ]; } }

Add metadata when constructing resource classes

You can also add top-level data when constructing resource class instances in the route or controller, which is valid in all resource classes additional Method, which can be used to add array data to the resource response:

 return (new UserCollection(User::all()->load('roles'))) ->additional(['meta' => [ 'key' => 'value', ]]);

Resource response

As you know, resource classes can be returned directly from routes and controllers:

 use App\User; use App\Http\Resources\UserResource; Route::get('/user', function () { return new UserResource(User::find(1)); });

However, sometimes you may need to customize the output HTTP response before sending it to the client. There are two ways to achieve this function, the first is linking response Method to resource class, this method will return a Illuminate\Http\Response Instance, which allows you to fully control the response header:

 use App\User; use App\Http\Resources\UserResource; Route::get('/user', function () { return (new UserResource(User::find(1))) ->response() ->header('X-Value', 'True'); });

Another method is to define a withResponse Method, which will be called when the resource is returned as the outermost data in the response:

 <? php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\Resource; class UserResource extends Resource { /** * Transform the resource into an array. * * @param  \Illuminate\Http\Request * @return array */ public function toArray($request) { return [ 'id' => $this->id, ]; } /** * Customize the outgoing response for the resource. * * @param  \Illuminate\Http\Request * @param  \Illuminate\Http\Response * @return void */ public function withResponse($request, $response) { $response->header('X-Value', 'True'); } }

give the thumbs-up Cancel Like Collection Cancel Collection

<<Previous: Formatting model data with accessors and modifiers

>>Next: Serialize model data into arrays or JSON