Вступление
При создании API вам может понадобиться слой преобразования, который находится между вашими моделями Eloquent и ответами JSON, которые фактически возвращаются пользователям вашего приложения. Классы ресурсов Laravel позволяют вам выразительно и легко преобразовать ваши модели и коллекции моделей в JSON.
Создание ресурсов
Для создания класса ресурса вы можете использовать команду Artisan. По умолчанию ресурсы будут размещаться в каталоге вашего приложения. Ресурсы расширяют класс:make:resource
app/Http/Resources
Illuminate\Http\Resources\Json\JsonResource
php artisan make:resource User
Коллекции Ресурсов
Помимо создания ресурсов, преобразующих отдельные модели, вы можете создавать ресурсы, отвечающие за преобразование коллекций моделей. Это позволяет вашему ответу включать ссылки и другую мета-информацию, относящуюся ко всей коллекции данного ресурса.
Чтобы создать коллекцию ресурсов, вы должны использовать --collection
флаг при создании ресурса. Или включение слова Collection
в имя ресурса будет указывать Laravel, что он должен создать ресурс коллекции. Ресурсы коллекции расширяют класс: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\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Коллекции Ресурсов
Если вы возвращаете коллекцию ресурсов или разбитый на страницы ответ, вы можете использовать этот collection
метод при создании экземпляра ресурса в вашем маршруте или контроллере:
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
{
/**
* 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());
});
Сохранение ключей коллекции
При возврате коллекции ресурсов из маршрута 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\User;
use App\Http\Resources\User as UserResource;
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\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Отношения
Если вы хотите включить связанные ресурсы в свой ответ, вы можете добавить их в массив, возвращаемый вашим toArray
методом. В этом примере мы будем использовать метод Post
ресурса, collection
чтобы добавить сообщения блога пользователя в ответ ресурса:
/**
* 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
метод для создания «специальной» коллекции ресурсов на лету:
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());
});
Перенос данных
По умолчанию ваш самый внешний ресурс оборачивается 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\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\Resource;
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();
}
}
Этот
withoutWrapping
метод влияет только на самый внешний ответ и не удаляетdata
ключи, которые вы вручную добавляете в свои собственные коллекции ресурсов.
Упаковка вложенных ресурсов
У вас есть полная свобода определять, как связаны ваши ресурсы. Если вы хотите, чтобы все коллекции ресурсов были заключены в 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];
}
}
Упаковка данных и разбиение на страницы
При возврате разбитых на страницы коллекций в ответе ресурса, Laravel обернет ваши данные ресурса в 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
}
}
Пагинация
Вы всегда можете передать экземпляр paginator collection
методу ресурса или пользовательской коллекции ресурсов:
use App\User;
use App\Http\Resources\UserCollection;
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
, эти атрибуты будут полностью удалены из ответа ресурса перед его отправкой клиенту.
Этот
mergeWhen
метод не должен использоваться в массивах, которые смешивают строковые и числовые ключи. Кроме того, он не должен использоваться в массивах с цифровыми клавишами, которые не упорядочены последовательно.
Условные Отношения
В дополнение к условной загрузке атрибутов, вы можете условно включать отношения в ваши ответы ресурса на основе того, были ли отношения уже загружены в модель. Это позволяет вашему контроллеру решать, какие отношения должны быть загружены в модель, и ваш ресурс может легко включать их только тогда, когда они действительно были загружены.
В конечном итоге это позволяет избежать проблем с запросами "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
метод. whenPivotLoaded
Метод принимает имя сводной таблицы в качестве первого аргумента. Второй аргумент должен быть Closure, который определяет значение, которое будет возвращено, если в модели доступна сводная информация:
/**
* 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
, которые автоматически добавляются Laravel при возврате разбитых на страницы ответов. Любые дополнительные, которые links
вы определяете, будут объединены со ссылками, предоставленными paginator.
Метаданные верхнего уровня
Иногда вы можете захотеть включить определенные метаданные в ответ ресурса, если ресурс является самым внешним возвращаемым ресурсом. Как правило, это включает метаинформацию об ответе в целом. Чтобы определить эти метаданные, добавьте 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\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
Однако иногда вам может потребоваться настроить исходящий HTTP-ответ перед его отправкой клиенту. Есть два способа сделать это. Во-первых, вы можете связать response
метод с ресурсом. Этот метод вернет экземпляр, что позволит вам полностью контролировать заголовки ответа:Illuminate\Http\Response
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
{
/**
* 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');
}
}
0 комментариев