Laravel (5.7) Ресурсы Eloquent API
Вступление
Когда мы создаем API, нам может понадобиться слой преобразования, который будет находиться между нашими моделями Eloquent и ответами JSON, которые фактически возвращаются пользователям нашего приложения. Ресурсные классы Laravel позволяют нам выразительно и легко преобразовывать наши модели и наши коллекции в JSON.
Создание ресурсов
Чтобы создать класс ресурсов, мы можем использовать команду make: resource Artisan. По умолчанию все ресурсы будут размещены в каталоге app / Http / Resources нашего приложения. Ресурсы расширят класс IlluminateHttpResourcesJsonJsonResource:
php artisan make:resource User
Коллекции Ресурсов
Помимо создания ресурсов, которые преобразуют отдельные модели, вы можете создавать ресурсы, которые отвечают за преобразование коллекций моделей. Это позволит вашему ответу включать ссылки и другую мета-информацию, которая будет иметь отношение ко всей коллекции данного ресурса.
Если вы хотите создать коллекцию ресурсов, используйте флаг --collection при создании ресурса. Или, если вы включите слово Collection в имя ресурса, это будет указывать Laravel, что он должен создать ресурс коллекции. Ресурсы Collection расширяют класс IlluminateHttpResourcesJsonResourceCollection:
php artisan make:resource Users -collection
php artisan make:resource UserCollection
Обзор концепции
Прежде чем мы углубимся во все варианты, доступные нам при написании ресурсов, давайте сначала рассмотрим, как ресурсы используются в Laravel. Каждый класс ресурсов представляет отдельную модель, которую необходимо преобразовать в структуру JSON. Например, рассмотрим простой класс ресурсов пользователя:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* this will transform the resource into an array.
*
* @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,
];
}
}
Весь класс ресурсов определяет метод toArray, который будет возвращать массив атрибутов, которые должны быть преобразованы в JSON при отправке ответа. Обратите внимание, что мы можем получить доступ к свойствам модели непосредственно из переменной $ this. Это связано с тем, что класс ресурсов автоматически проксирует доступ к методу и свойству до базовой модели для удобного доступа. Сразу же ресурс определен, его можно вернуть с маршрута или контроллера:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Коллекции Ресурсов
В случае, когда вы возвращаете коллекцию ресурсов или вы возвращаете разбитый на страницы ответ, вы можете использовать метод сбора при создании экземпляра ресурса в вашем маршруте или контроллере:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return UserResource::collection(User::all());
});
Обратите внимание, что это не позволяет добавлять метаданные, которые, возможно, потребуется вернуть вместе с коллекцией. Если вы хотите настроить ответ коллекции ресурсов, вы можете создать выделенный ресурс для представления коллекции:
php artisan make:resource UserCollection
После того, как вы сгенерируете класс сбора ресурсов, вы можете легко определить любые метаданные, которые должны быть включены в ответ:
'<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* this transforms the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
После того как вы определили свою коллекцию ресурсов, она может быть возвращена из маршрута или контроллера:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Настройка базового класса ресурсов
Как правило, свойство $ this-> collection коллекции ресурсов автоматически заполняется с результатом сопоставления каждого элемента коллекции с его единственным классом ресурсов. Предполагается, что единственным типом ресурса будет имя класса коллекции без завершающей строки Collection.
Например, UserCollection попытается отобразить данные пользовательские экземпляры в пользовательский ресурс. Если вы хотите настроить это поведение, вы можете переопределить свойство $ collects вашей коллекции ресурсов:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* This is the resource that this resource collects.
*
* @var string
*/
public $collects ='App\Http\Resources\Member';
}
Письменные Ресурсы
Ресурсы очень просты, и это факт. Им нужно будет только преобразовать данную модель в массив. Итак, каждый ресурс содержит метод toArray, который преобразует атрибуты вашей модели в дружественный к API массив, который может быть возвращен вашим пользователям:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* Transforms the resource into an array.
*
* @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,
];
}
}
Как только ресурс определен, его можно вернуть непосредственно из маршрута или контроллера:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Отношения
Если вы хотите включить связанные ресурсы в свой ответ, вы можете добавить их в массив, возвращаемый вашим методом toArray. В приведенном ниже примере мы будем использовать метод сбора ресурса Post, чтобы добавить сообщения блога пользователя в ответ ресурса:
/**
* Transforms the resource into an array.
*
* @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,
];
}
Коллекции Ресурсов
Ресурс преобразует одну модель в массив, а коллекция ресурсов преобразует коллекцию моделей в массив. Не обязательно определять класс сбора ресурсов для всех типов вашей модели, поскольку все ресурсы будут предоставлять метод сбора для создания коллекции ad-hoc ресурсов на лету:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return UserResource::collection(User::all());
});
Однако, если вам нужно настроить метаданные, возвращаемые вместе с коллекцией, необходимо определить коллекцию ресурсов:
<?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 $request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
Как и отдельные ресурсы, коллекции ресурсов могут быть возвращены непосредственно с маршрутов или контроллеров:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Перенос данных
По умолчанию ваш самый внешний ресурс будет заключен в ключ данных, когда ответ ресурса преобразуется в JSON. Типичный ответ сбора ресурсов будет выглядеть следующим образом:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]",
}
]
}
Если вы хотите отключить перенос самого внешнего ресурса, вы можете использовать методlessWrapping в базовом классе ресурсов. В типичных случаях вам нужно вызвать этот метод из вашего AppServiceProvider или любого другого поставщика услуг, который загружается при каждом запросе к вашему приложению:
<?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()
{
//
}
}
Упаковка вложенных ресурсов
У вас есть полная свобода определять, как связаны ваши ресурсы. Если вы хотите, чтобы все коллекции ресурсов были обернуты в ключ данных, независимо от их вложенности, вы должны определить класс сбора ресурсов для каждого ресурса и вернуть коллекцию в ключе данных.
К настоящему времени вы должны задаться вопросом, не приведет ли это к тому, что ваш самый внешний ресурс будет обернут в два ключа данных. Не беспокойтесь, Laravel никогда не допустит двойного переноса ваших ресурсов, поэтому вам не нужно беспокоиться об уровне вложенности преобразуемой коллекции ресурсов:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CommentsCollection extends ResourceCollection
{
/**
* Transforms the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return ['data' => $this->collection];
}
}
Упаковка данных и разбиение на страницы
Когда вы возвращаете разбитые на страницы коллекции в ответе ресурса, Laravel обернет ваши данные ресурса в ключ данных, даже если вы вызвали метод без использования обертки. Это связано с тем, что постраничные ответы всегда будут содержать ссылки и мета-ключи с информацией о состоянии пагинатора:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]",
}
],
"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
}
}
пагинация
Вы всегда можете передать экземпляр paginator методу коллекции ресурса или пользовательской коллекции ресурсов:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
Разбитые на страницы ответы всегда будут содержать ссылки и мета-ключи с информацией о состоянии пагинатора:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]",
}
],
"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
}
}
Условные атрибуты
Иногда вы хотите включить атрибут в ответ ресурса, только если данное условие выполнено. Например, вы можете захотеть включить значение, только если текущий пользователь является «администратором». Laravel предоставляет различные вспомогательные методы, которые помогут вам в этой ситуации. Метод when можно использовать для условного добавления атрибута в ответ ресурса:
/**
* Transforms the resource into an array.
*
* @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,
];
}
В приведенном выше примере секретный ключ возвращается только в окончательном ответе ресурса, если метод isAdmin аутентифицированного пользователя возвращает true. В случае, когда метод возвращает значение false, секретный ключ полностью удаляется из ответа ресурса перед его отправкой обратно клиенту. Метод when позволит вам выразительно определить ваши ресурсы, не прибегая к условным операторам при построении массива.
Метод when также примет Closure в качестве второго аргумента, это позволяет вычислять результирующее значение, только если данное условие истинно:
'secret' => $this->when(Auth::user()->isAdmin(), function () {
return 'secret-value';
}),
Слияние условных атрибутов
Есть моменты, когда у вас есть несколько атрибутов, которые должны быть включены только в ответ ресурса на основе одного и того же условия. В таких случаях вы можете использовать метод mergeWhen для включения атрибутов в ответ только тогда, когда заданное условие истинно:
/**
* Transforms the resource into an array.
*
* @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,
];
}
Опять же, когда заданное условие ложно, эти атрибуты полностью удаляются из ответа ресурса перед их отправкой клиенту.
Условные Отношения
Помимо условной загрузки атрибутов, вы можете условно включать отношения в ответы ресурсов, основываясь на том, были ли эти отношения уже загружены в модель. Это позволит вашему контроллеру решить, какие отношения должны быть загружены в модель, и ваш ресурс будет легко включать их только тогда, когда они действительно были загружены.
В конечном итоге это поможет избежать проблем с запросами "N + 1" в ваших ресурсах. Метод whenLoaded может использоваться для условной загрузки отношения. Чтобы избежать ненужной загрузки отношений, этот метод примет имя отношения вместо самого отношения:
/**
* The resource is transformed into an array.
*
* @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,
];
}
В приведенном выше примере, если отношение не было загружено, ключ posts полностью удаляется из ответа ресурса перед его отправкой клиенту.
Условная сводная информация
Помимо условного включения информации об отношениях в ответы ресурсов, вы можете условно включать данные из промежуточных таблиц отношений «многие ко многим», используя метод whenPivotLoaded. Метод whenPivotLoaded примет имя сводной таблицы в качестве первого аргумента. Второй аргумент должен быть Closure, который определяет значение, которое будет возвращено, если в модели доступна сводная информация:
/**
* The resource will be transformed into an array.
*
* @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;
}),
];
}
Если ваша промежуточная таблица использует метод доступа, а не сводную, вы можете использовать метод whenPivotLoadedAs:
/**
* Transform the resource into an array.
*
* @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;
}),
];
}
Добавление метаданных
Некоторые стандарты JSON API потребуют добавления метаданных к вашим ресурсам и ответам коллекций ресурсов. Это часто включает в себя такие вещи, как ссылки на ресурс или связанные ресурсы, или метаданные о самом ресурсе. Если вам нужно вернуть дополнительные метаданные о ресурсе, вы должны включить его в свой метод toArray. Например, вы можете включить информацию о ссылках при преобразовании коллекции ресурсов:
/**
* Transforms the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
Когда вы возвращаете дополнительные метаданные из своих ресурсов, вам не нужно беспокоиться о случайном переопределении ключей мета или ссылок, которые автоматически добавляются Laravel при возврате разбитых на страницы ответов. Все дополнительные ссылки, которые вы определяете, будут объединены со ссылками, предоставленными paginator.
Метаданные верхнего уровня
Бывают ситуации, когда вам может потребоваться включить определенные метаданные в ответ ресурса только в том случае, если ресурс является наиболее внешним возвращаемым ресурсом. Как правило, это будет включать метаинформацию об ответе в целом. Если вы хотите определить эти метаданные, вы должны добавить метод with в свой класс ресурсов. Этот метод должен возвращать массив метаданных, которые будут включены в ответ ресурса, только когда ресурс является самым внешним ресурсом, который отображается:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transforms the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
/**
* Get additional data that has to be returned with the resource array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function with($request)
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
Добавление метаданных при построении ресурсов
Вы также можете добавить данные верхнего уровня при создании экземпляров ресурса в вашем маршруте или контроллере. Этот дополнительный метод, доступный на всех ресурсах, будет принимать массив данных, который должен быть добавлен к ответу ресурса:
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);
Ответы ресурса
Как вы уже прочитали, ресурсы могут быть возвращены непосредственно с маршрутов и контроллеров:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Однако иногда вы можете захотеть настроить исходящий HTTP-ответ перед его отправкой клиенту. Есть два способа добиться этого. Во-первых, вы можете связать метод ответа с ресурсом. Этот метод возвращает экземпляр IlluminateHttpResponse, позволяя вам полностью контролировать заголовки ответа:
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');
});
Кроме того, вы можете определить метод withResponse в самом ресурсе. Этот метод вызывается, когда ресурс возвращается как самый внешний ресурс в ответе:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* Transforming the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
];
}
/**
* Customizing the outgoing response for the resource.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function withResponse($request, $response)
{
$response->header('X-Value', 'True');
}
}
Новый контент: Composer: менеджер зависимостей для PHP , R программирования