Это общий обзор ресурсов и коллекции ресурсов. Вам рекомендуется прочитать другую секцию документации,
чтобы получить более глубокое понимание возможностей настройки и мощи решения
предложенного вам путем использования ресурсов.
API ресурсы
Содержание:
Введение
При построении API, вам может понадобиться трансформационный слой, который располагается между вашими моделями Eloquent и ответами JSON, которые и возвращаются пользователями вашего приложения. Класс ресурсов Laravel позволяет вам легко трансформировать ваши модели и коллекции моделей в JSON.
Генерация ресурсов
Для генерации класса ресурса, вы можете использовать команду Artisan make:resource
.
По умолчанию, ресурсы будут размещаться в директории вашего приложения app/Http/Resources
.
Ресурсы расширяют класс Illuminate\Http\Resources\Json\JsonResource
:
php artisan make:resource User
Коллекции ресурсов
В дополнение к генерации ресурсов, которые трансформируют отдельные модели, вы можете генерировать ресурсы, которые отвечают за трансформацию коллекции моделей. Это позволяет вашим ответам включать ссылки и другую мета-информацию, которая релевантная для всей коллекции для данного ресурса.
Для создания коллекции ресурсов, вам необходимо использовать опцию --collection
при создании ресурса. Или включение слова Collection
в имя ресурса,
будет знаком для Ларавел необходимо создать коллекцию ресурса. Коллекцию ресурсов расширяет
класс Illuminate\Http\Resources\Json\ResourceCollection
:
php artisan make:resource Users --collection
php artisan make:resource UserCollection
Обзор концепции
Перед тем как преступить к обзоры всех опций доступных вам при написании ресурсов,
давайте посмотрим на то, как русурсы используются внутри Laravel. Класс ресурсов представляет
отдельную модель, которую необходимо трансформировать в JSON структуру. Например, посмортим на
простой класс ресурса User
:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* 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\Http\Resources\User as UserResource;
use App\User;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Коллекции ресурсов
Если вы возвращаете коллекцию ресурсов или ресурсы с постраничным делением,
вы можете использовать метод collection
при создании экземпляра ресурса
в вашем маршруте или контроллере:
use App\Http\Resources\User as UserResource;
use App\User;
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
{
/**
* 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\Http\Resources\UserCollection;
use App\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Сохранение ключей коллекции
При возвращение коллекции ресурса из маршрута, Laravel сбрасывает ключи коллекции,
поэтому они будут расположены в простом числовом порядке. Однако, вы можете добавить свойство
preserveKeys
в класс вашего ресурса, который является индикатором и показывает,
что ключи должены быть сохранены:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* Indicates if the resource's collection keys should be preserved.
*
* @var bool
*/
public $preserveKeys = true;
}
Когда значение свойства preserveKeys
устанавливается как true
,
ключи коллекции будут сохранены:
use App\Http\Resources\User as UserResource;
use App\User;
Route::get('/user', function () {
return UserResource::collection(User::all()->keyBy->id);
});
Настройка основного класса ресурса
Обычно, свойство коллекции ресурса $this->collection
автоматически наполняется
результатом сопоставления каждого элемента коллекции с его единственным классом ресурса.
Предполагается, что единственным классом ресурса является именем класса коллекции без
окончания Collection
.
Например, UserCollection
будет пытаться отобразить данный экземпляр пользователя
в ресурс User
. Для настройки данного поведения, вы можете переписать свойство
$collects
вашей коллекции ресурса:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* 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
{
/**
* 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,
];
}
}
После определения ресурса, его можно вернуть напрямую из маршрута или контроллера:
use App\Http\Resources\User as UserResource;
use App\User;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Связи
Если вы хотите включить связанные ресурсы в ответ, вы можете добавить их в массив,
возвращаемый методом toArray
. В этом примере мы будем использовать
метод collection
для ресурса Post
, чтобы добавить заметки пользователя
в ответ ресурса:
/**
* 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,
'posts' => PostResource::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Коллекции ресурсов
В то время как обычные ресурсы преобразовывают одиночную модель в массив, коллекция ресурсов
преобразовывает коллекцию моделей в массив. Нет необходимости определять класс коллекции ресурсов
для каждой из ваших моделей, в силу того, что ресурсы предоставляют метод collection
,
чтобы генерировать "ad-hoc" («по особому случаю») коллекцию ресурсов на "лету":
use App\Http\Resources\User as UserResource;
use App\User;
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\Http\Resources\UserCollection;
use App\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Обёртка данных
По умолчанию, данные большинства ресурсов обёрнуто в ключ data
при преобразовании
ответа ресурса в JSON. Напрнмер, типовой ответ для коллекции ресурсов выглядит следующим образом:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
]
}
Если вы хотите отлючить обёртывание большинства ресурсов, вы можете использовать
метод withoutWrapping
для базового класса ресурса. Обычно, вы должны вызвать этот метод
из вашего AppServiceProvider
или другого сервис-провадера, который загружается
для каждого запроса в ваше приложение:
<?php
namespace App\Providers;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Resource::withoutWrapping();
}
}
Обёртка вложенных ресурсов
У вас есть полная свобода в том, чтобы определить как оборачивать связи ресурса.
Если вы хотите, чтобы все коллекции ресурсов оборачивались с ключом data
,
вне зависимости от их вложенности, то вам необходимо определить класс ресурса коллекции
для каждого ресурса и вернуть коллекцию внутри ключа data
.
Может возникнуть беспокойство, что в этом случае большинство ресурсво будут обёрнуты
в два ключа data
. Не нужно беспокоиться, Laravel не позволит ресурсам быть дважды обёрнутыми,
не нужно волноваться о вложенности уровней коллекции ресурса при трансформации:
<?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 $request
* @return array
*/
public function toArray($request)
{
return ['data' => $this->collection];
}
}
Оборачивание данныз и пагинация
При возвращении коллекций с постраничным делением, Ларавел будет оборачивать данных
в ключ data
, даже если метод withoutWrapping
вызывался.
Так происходит в силу того, что ответы пагинации всегда содержат ключи meta
и links
с информацией о состоянии пагинатора:
{
"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
}
}
Пагинация
Вы всегда можете передавать экземпляр пагинатора в метод ресурса collection
или в пользовательскую коллекцию ресурса:
use App\Http\Resources\UserCollection;
use App\User;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
Ответы с пагинацией всегда содержат ключи meta
и links
с информацией
о состоянии пагинатора:
{
"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
}
}
Условные атрибуты
Бывают случаи, когда вам необходимо включить атрибут в ответ ресурса, если данное услвоие верно.
Например. вы можете включить значение, если текущий пользователь является "администратором".
Laravel предоствляет набор полезных методов, чтобы помочь вам в такой ситуации.
Метод when
может быть использован для добавления атрибута при соблидении
определённых условий в ответ ресурса:
/**
* 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,
'secret' => $this->when(Auth::user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
В этом примере, ключ secret
будет возвращен в финальном ответе ресураса,
если метод isAdmin
вернёт true
.
Если метод возвращает false
, ключ secret
будет убран из ответа ресурса полностью
до того, как отправят обратно клиенту. Метод when
позволяет вам определить содержание ресурса
без условных выражений во время построения массива.
Метод when
также принимает замыкание в качестве второго аргумента,
что позволяет вам подсчитать конечное значение, если данное условие true
:
'secret' => $this->when(Auth::user()->isAdmin(), function () {
return 'secret-value';
}),
Соединение условных атрибутов
Иногда у вас может быть несколько атрибутов, которые должны быть включены в ответ русерса
на основании некоторых условий. В этом случае, вы можете использовать метод mergeWhen
для включения атрибутов в ответ, когда условие true
:
/**
* 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,
$this->mergeWhen(Auth::user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Опять же, если данное условие false
, эти атрибуты будут удалены из ответа ресурса полностью
до момента отправки на клиент.
Условные отношения
Дополнительно к загрузке атрибутов при выполнении определённых условий, вы также при выполнения условий можете включать отношения в ответ вашего ресурса в зависимости от того, что отношение уже загружено для модели. Это позволяет вашему контроллеру решать какие отношения необходимо загрузить для модели и ваш ресурс может легко включить их только, если они были фактически загружены.
В конечном счёте это позволяет лёгко избежать проблему с запросами "N+1" внутри ваших ресурсов.
Метод whenLoaded
может быть использован для загрузки отношений по условиям.
Чтобы избежать ненужной загрузки отношений, этот метод принимает имя отношения, вместо
самого отношения:
/**
* 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,
'posts' => PostResource::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
В этом примере, если отношение не было загружено, ключ posts
будет убран
из ответа ресурса полностью до момента отправки его на клиент.
Условная сводная информацию
Дополнительно ко включению информации связей в ваше ответ ресурса при достижении некоторых условий,
вы можете условно включить данные из промежуточных таблиц для связи многие-ко-многим используя
метод whenPivotLoaded
. Этот метод принимает имя сводной таблицы в качестве первого
аргумента. Второй аргумент должен быть Замыканием, которое определит значение,
которое необходимо вернуть, если сводная инфомрацию доступна для модели:
/**
* 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->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}
Если ваша промежуточная таблица использует отличный от pivot
аксессор,
вы можете использовать метод 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 стандарты требуют дополнительные мета-данные в ответы для вашего ресурса
и коллекции ресурсов. Часто включаются такие вещи как links
ссылки на ресурс
или связаныые ресурсы, или мета-данные о самом ресурсе. Если необходимо вернуть дополнительные
мета-данные о ресурсе, включите их в ваш метод toArray
. Например, вам необходимо
включить информацию о ссылке link
при преобразовании коллекции ресурса:
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
При возвращении дополнительные мета-данные из вашего ресурса, вам никогда не нужно волноваться
о возможной случайной перезаписи ключей links
или meta
,
которые автоматически добавляются Ларавел, при возвращении ресурсов с пагинацией.
Любые дополнительные ссылки links
, которые вы определили, будут соединены с ссылками
пагинатора.
Мета-данные верхнего уровня
Иногда вы можете захотеть лишь включить некоторые мета-данные с ответом ресурса,
если ресурс является самым верхним ресурсом из ответа. Обычно, такой запрос вернёт
мета-информацию о самом ответе в целом. Чтобы определить эти мета-данные, добавьте метод with
в ваш класс ресурса. Этот метод должен вернуть массив мета-данные, которые необходимо включать
в ответ ресурса только в том случае, когда ресурс отобразится:
<?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 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',
],
];
}
}
Добавление мета-данные при построении ресурсов
Вы можете добавить данные верхнего уровня при построении экземпляра ресурса
в маршрут или контроллер. Метод additional
, которые доступен для всех ресурсов,
принимает массив данных, которые должны быть добавлены в ответ ресурса:
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);
Ответы ресурсов
Как упоминалось ранее, ресурсы могут быть возвращены напрямую из маршрута или контроллера:
use App\Http\Resources\User as UserResource;
use App\User;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Однако, иногда вам может потребоваться настроить исходящий HTTP ответ до момента отправки на клиент.
Есть два способа реализовать это. Первый способ заключается в добавлении цепочкой метода response
для ресурса. Этот метод вернёт экземпляр Illuminate\Http\Response
, что позволяет вам
полность контролировать заголовки ответа:
use App\Http\Resources\User as UserResource;
use App\User;
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
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
];
}
/**
* Customize 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');
}
}