Документация Laravel 10.x
Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.
При создании API вам может потребоваться слой трансформации, который находится между вашие моделями Eloquent и JSON-ответами, которые фактически возвращаются пользователям вашего приложения. Например, вы можете отображать определенные атрибуты для подмножества пользователей и не для других, или всегда включать определенные отношения в JSON-представлении ваших моделей. Ресурсы Eloquent позволяют вам выразительно и легко преобразовывать ваши модели и коллекции моделей в JSON.
Конечно, вы всегда можете преобразовывать модели или коллекции Eloquent в JSON с использованием их методов toJson
; однако ресурсы Eloquent предоставляют более детальный и надежный контроль над JSON-сериализацией ваших моделей и их отношений.
Для создания класса ресурса вы можете использовать команду Artisan make:resource
. По умолчанию ресурсы будут размещены в каталоге app/Http/Resources
вашего приложения. Ресурсы расширяют класс Illuminate\Http\Resources\Json\JsonResource
:
php artisan make:resource UserResource
Помимо создания ресурсов, которые преобразуют отдельные модели, вы можете создавать ресурсы, которые отвечают за преобразование коллекций моделей. Это позволяет включать в ваши JSON-ответы ссылки и другую мета-информацию, которая относится ко всей коллекции заданного ресурса.
Для создания коллекции ресурсов вы должны использовать флаг --collection
при создании ресурса. Или, включение слова Collection
в имя ресурса указывает Laravel, что нужно создать коллекцию ресурсов. Классы коллекций ресурсов расширяют класс Illuminate\Http\Resources\Json\ResourceCollection
:
php artisan make:resource User --collection php artisan make:resource UserCollection
Примечание Это общий обзор ресурсов и коллекций ресурсов. Настоятельно рекомендуется прочитать другие разделы этой документации, чтобы получить более глубокое понимание возможностей и средств, предоставляемых вам ресурсами.
Прежде чем погружаться во все ваши варианты при написании ресурсов, давайте сначала обзорно рассмотрим, как ресурсы используются в Laravel. Класс ресурса представляет собой одну модель, которую необходимо преобразовать в структуру JSON. Например, вот простой класс ресурса UserResource
:
<?php namespace App\Http\Resources; use Illuminate\Http\Request;use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource{ /** * Преобразует ресурс в массив. * * @return array<string, mixed> */ public function toArray(Request $request): array { 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\UserResource;use App\Models\User; Route::get('/user/{id}', function (string $id) { return new UserResource(User::findOrFail($id));});
Если вы возвращаете коллекцию ресурсов или ответ с пагинацией, вы должны использовать метод collection
, предоставленный вашим классом ресурса, при создании экземпляра ресурса в вашем маршруте или контроллере:
use App\Http\Resources\UserResource;use App\Models\User; Route::get('/users', function () { return UserResource::collection(User::all());});
Обратите внимание, что это не позволяет добавлять пользовательские метаданные, которые могут быть возвращены вместе с вашей коллекцией. Если вы хотите настроить ответ на коллекцию ресурсов, вы можете создать отдельный ресурс для представления коллекции:
php artisan make:resource UserCollection
После того как класс коллекции ресурсов был сгенерирован, вы можете легко определить любые метаданные, которые должны включаться в ответ:
<?php namespace App\Http\Resources; use Illuminate\Http\Request;use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * Преобразует коллекцию ресурсов в массив. * * @return array<int|string, mixed> */ public function toArray(Request $request): array { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; }}
После определения вашей коллекции ресурсов ее можно вернуть из маршрута или контроллера:
use App\Http\Resources\UserCollection;use App\Models\User; Route::get('/users', function () { return new UserCollection(User::all());});
При возврате коллекции ресурсов из маршрута Laravel сбрасывает ключи коллекции так, чтобы они имели числовой порядок. Однако вы можете добавить свойство preserveKeys
к своему классу ресурса, указывающее, следует ли сохранять исходные ключи коллекции:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource{ /** * Указывает, следует ли сохранять ключи коллекции ресурсов. * * @var bool */ public $preserveKeys = true;}
Когда свойство preserveKeys
установлено в true
, ключи коллекции будут сохранены при возврате коллекции из маршрута или контроллера:
use App\Http\Resources\UserResource;use App\Models\User; Route::get('/users', function () { return UserResource::collection(User::all()->keyBy->id);});
Обычно свойство $this->collection
коллекции ресурса автоматически заполняется результатом сопоставления каждого элемента коллекции с ее собственным ресурсом. Предполагается, что сингулярный класс ресурса - это имя класса коллекции без завершающей части Collection
. Кроме того, в зависимости от вашего личного вкуса сингулярный класс ресурса может быть с суффиксом Resource
или без него.
Например, UserCollection
будет пытаться сопоставить заданные экземпляры пользователя с ресурсом UserResource
. Чтобы настроить это поведение, вы можете переопределить свойство $collects
вашей коллекции ресурсов:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * Ресурс, который собирает этот ресурс. * * @var string */ public $collects = Member::class;}
Примечание Если вы еще не читали обзор концепции, настоятельно рекомендуется сделать это перед изучением этой документации.
Ресурсы должны преобразовывать заданную модель в массив. Таким образом, каждый ресурс содержит метод toArray
, который преобразует атрибуты вашей модели в дружественный к API массив, который может быть возвращен из маршрутов или контроллеров вашего приложения:
<?php namespace App\Http\Resources; use Illuminate\Http\Request;use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource{ /** * Преобразует ресурс в массив. * * @return array<string, mixed> */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }}
После того как ресурс был определен, его можно вернуть напрямую из маршрута или контроллера:
use App\Http\Resources\UserResource;use App\Models\User; Route::get('/user/{id}', function (string $id) { return new UserResource(User::findOrFail($id));});
Если вы хотите включить связанные ресурсы в ваш ответ, вы можете добавить их в массив, возвращаемый методом toArray
вашего ресурса. В этом примере мы будем использовать метод collection
ресурса PostResource
для добавления блог-постов пользователя в ответ ресурса:
use App\Http\Resources\PostResource;use Illuminate\Http\Request; /** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ 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\Http\Resources\UserResource;use App\Models\User; Route::get('/users', function () { return UserResource::collection(User::all());});
Однако, если вам нужно настроить метаданные, возвращаемые с коллекцией, необходимо определить свою собственную коллекцию ресурсов:
<?php namespace App\Http\Resources; use Illuminate\Http\Request;use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * Преобразует коллекцию ресурсов в массив. * * @return array<string, mixed> */ public function toArray(Request $request): array { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; }}
Как и с сингулярными ресурсами, коллекции ресурсов могут быть возвращены напрямую из маршрутов или контроллеров:
use App\Http\Resources\UserCollection;use App\Models\User; Route::get('/users', function () { return new UserCollection(User::all());});
По умолчанию ваш самый внешний ресурс оборачивается в ключ data
, когда ответ ресурса преобразуется в JSON. Таким образом, например, ответ на коллекцию ресурсов выглядит следующим образом:
{ "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", }, { "id": 2, "name": "Liliana Mayert", } ]}
Если вы хотите использовать пользовательский ключ вместо data
, вы можете определить атрибут $wrap
в классе ресурса:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource{ /** * Обертка "data", которую следует применить. * * @var string|null */ public static $wrap = 'user';}
Если вы хотите отключить оборачивание самого внешнего ресурса, вы должны вызвать метод withoutWrapping
на базовом классе Illuminate\Http\Resources\Json\JsonResource
. Обычно вы должны вызывать этот метод из вашего AppServiceProvider
или другого поставщика услуг, который загружается при каждом запросе к вашему приложению:
<?php namespace App\Providers; use Illuminate\Http\Resources\Json\JsonResource;use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider{ /** * Регистрирует сервисы приложения. */ public function register(): void { // ... } /** * Запускает любые службы приложения. */ public function boot(): void { JsonResource::withoutWrapping(); }}
Внимание Метод
withoutWrapping
влияет только на внешний ответ и не удаляет ключиdata
, которые вы вручную добавляете в свои собственные коллекции ресурсов.
У вас есть полная свобода определения того, как оборачиваются отношения вашего ресурса. Если вы хотите, чтобы все коллекции ресурсов оборачивались в ключ data
, независимо от их вложенности, вы должны определить класс коллекции ресурсов для каждого ресурса и возвращать коллекцию внутри ключа data
.
Возможно, вы задаетесь вопросом, не приведет ли это к тому, что ваш самый внешний ресурс будет упакован в два ключа data
. Не волнуйтесь, Laravel никогда не позволит случайно дважды обернуть ваши ресурсы, так что вам не нужно беспокоиться о уровне вложенности коллекции ресурсов, которую вы преобразуете:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class CommentsCollection extends ResourceCollection{ /** * Преобразует коллекцию ресурсов в массив. * * @return array<string, mixed> */ public function toArray(Request $request): array { return ['data' => $this->collection]; }}
При возврате пагинированных коллекций через ответ ресурса Laravel будет оборачивать данные вашего ресурса в ключ data
, даже если метод withoutWrapping
был вызван. Это потому, что ответы с пагинацией всегда содержат ключи meta
и links
с информацией о состоянии пагинатора:
{ "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", }, { "id": 2, "name": "Liliana Mayert", } ], "links":{ "first": "http://example.com/users?page=1", "last": "http://example.com/users?page=1", "prev": null, "next": null }, "meta":{ "current_page": 1, "from": 1, "last_page": 1, "path": "http://example.com/users", "per_page": 15, "to": 10, "total": 10 }}
Вы можете передать экземпляр пагинатора Laravel методу collection
ресурса или пользовательской коллекции ресурсов:
use App\Http\Resources\UserCollection;use App\Models\User; Route::get('/users', function () { return new UserCollection(User::paginate());});
Ответы с пагинацией всегда содержат ключи meta
и links
с информацией о состоянии пагинатора:
{ "data": [ { "id": 1, "name": "Eladio Schroeder Sr.", }, { "id": 2, "name": "Liliana Mayert", } ], "links":{ "first": "http://example.com/users?page=1", "last": "http://example.com/users?page=1", "prev": null, "next": null }, "meta":{ "current_page": 1, "from": 1, "last_page": 1, "path": "http://example.com/users", "per_page": 15, "to": 10, "total": 10 }}
Если вы хотите настроить информацию, включаемую в ключи links
или meta
ответа с пагинацией, вы можете определить метод paginationInformation
в ресурсе. Этот метод будет получать данные $paginated
и массив $default
с информацией, содержащим ключи links
и meta
:
/** * Настраивает информацию о пагинации для ресурса. * * @param \Illuminate\Http\Request $request * @param array $paginated * @param array $default * @return array */public function paginationInformation($request, $paginated, $default){ $default['links']['custom'] = 'https://example.com'; return $default;}
Иногда вы можете захотеть включить атрибут в ответ ресурса только в том случае, если выполняется определенное условие. Например, вы можете захотеть включить значение только в том случае, если текущий пользователь является "администратором". Laravel предоставляет различные вспомогательные методы, чтобы помочь вам в этой ситуации. Метод when
можно использовать для условного добавления атрибута в ответ ресурса:
/** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'secret' => $this->when($request->user()->isAdmin(), 'secret-value'), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ];}
В этом примере ключ secret
будет возвращен в конечном ответе ресурса только в том случае, если метод isAdmin
аутентифицированного пользователя возвращает true
. Если метод возвращает false
, ключ secret
будет удален из ответа ресурса перед его отправкой клиенту. Метод when
позволяет вам выразительно определять ваши ресурсы, не прибегая к условным операторам при построении массива.
Метод when
также принимает замыкание в качестве второго аргумента, что позволяет вам вычислить конечное значение только в том случае, если данное условие истинно:
'secret' => $this->when($request->user()->isAdmin(), function () { return 'modify_10x/eloquent-resources.return_text_541';}),
Метод whenHas
можно использовать для включения атрибута, если он действительно присутствует в основной модели:
'name' => $this->whenHas('name'),
Кроме того, метод whenNotNull
можно использовать для включения атрибута в ответ ресурса, если атрибут не равен null
:
'name' => $this->whenNotNull($this->name),
Иногда у вас может быть несколько атрибутов, которые должны включаться в ответ ресурса только при выполнении одного и того же условия. В этом случае вы можете использовать метод mergeWhen
, чтобы включать атрибуты в ответ только при условии, что заданное условие истинно:
/** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, $this->mergeWhen($request->user()->isAdmin(), [ 'first-secret' => 'value', 'second-secret' => 'value', ]), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ];}
Опять же, если заданное условие ложно, эти атрибуты будут удалены из ответа ресурса перед отправкой его клиенту.
Внимание Метод
mergeWhen
не следует использовать внутри массивов, в которых смешиваются строковые и числовые ключи. Кроме того, его не следует использовать внутри массивов с числовыми ключами, которые не упорядочены последовательно.
Помимо условной загрузки атрибутов, вы можете условно включать отношения в ответах на ваши ресурсы, основываясь на том, было ли отношение загружено на модель. Это позволяет вашему контроллеру решать, какие отношения следует загружать на модель, и ваш ресурс может легко включать их только тогда, когда они действительно были загружены. В конечном итоге это облегчает избегание проблем с "N+1" запросами в ваших ресурсах.
Метод whenLoaded
можно использовать для условной загрузки отношения. Чтобы избежать ненужной загрузки отношений, этот метод принимает имя отношения вместо самого отношения:
use App\Http\Resources\PostResource; /** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ 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
будет удален из ответа ресурса перед отправкой его клиенту.
Помимо условного включения отношений, вы можете условно включать счетчики отношений в ответах на ваши ресурсы, основываясь на том, было ли количество отношений загружено на модель:
new UserResource($user->loadCount('posts'));
Метод whenCounted
можно использовать для условного включения счетчика отношений в ответ ресурса. Этот метод избегает ненужного включения атрибута, если счетчик отношений отсутствует:
/** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts_count' => $this->whenCounted('posts'), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ];}
В этом примере, если количество отношений posts
не было загружено, ключ posts_count
будет удален из ответа ресурса перед отправкой его клиенту.
Другие типы агрегаций, такие как avg
, sum
, min
и max
, также могут быть условно загружены с использованием метода whenAggregated
:
'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),'words_min' => $this->whenAggregated('posts', 'words', 'min'),'words_max' => $this->whenAggregated('posts', 'words', 'max'),
Помимо условного включения информации об отношениях в ваши ответы ресурсов, вы можете условно включать данные из промежуточных таблиц многие ко многим с использованием метода whenPivotLoaded
. Метод whenPivotLoaded
принимает имя таблицы связи в качестве первого аргумента. Вторым аргументом должно быть замыкание, которое возвращает значение, которое следует возвращать, если информация о связи доступна на модели:
/** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ return [ 'id' => $this->id, 'name' => $this->name, 'expires_at' => $this->whenPivotLoaded('role_user', function () { return $this->pivot->expires_at; }), ];}
Если ваше отношение использует свою модель промежуточной таблицы, вы можете передать экземпляр модели промежуточной таблицы в качестве первого аргумента методу whenPivotLoaded
:
'expires_at' => $this->whenPivotLoaded(new Membership, function () { return $this->pivot->expires_at;}),
Если ваша промежуточная таблица использует метод доступа, отличный от pivot
, вы можете использовать метод whenPivotLoadedAs
:
/** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ 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
при преобразовании коллекции ресурсов:
/** * Преобразует ресурс в массив. * * @return array<string, mixed> */public function toArray(Request $request): array{ return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ];}
Возвращая дополнительные метаданные из ваших ресурсов, вам никогда не придется беспокоиться о том, что вы случайно перезапишете ключи links
или meta
, которые автоматически добавляются Laravel при возврате пагинированных ответов. Любые дополнительные links
, которые вы определите, будут объединены с ссылками, предоставленными пагинатором.
Иногда вам может потребоваться включить определенные метаданные только в ответ ресурса, если ресурс является внешним ресурсом, который возвращается. Обычно это включает в себя метаинформацию об ответе в целом. Чтобы определить эти метаданные, добавьте метод with
в свой класс ресурса. Этот метод должен возвращать массив метаданных, которые следует включать в ответ ресурса только в том случае, если ресурс является внешним ресурсом, который преобразуется:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection{ /** * Преобразует коллекцию ресурсов в массив. * * @return array<string, mixed> */ public function toArray(Request $request): array { return parent::toArray($request); } /** * Получает дополнительные данные, которые следует вернуть вместе с массивом ресурсов. * * @return array<string, mixed> */ public function with(Request $request): array { return [ 'meta' => [ 'key' => 'value', ], ]; }}
Вы также можете добавлять данные верхнего уровня при создании экземпляров ресурсов в вашем маршруте или контроллере. Метод additional
, доступный для всех ресурсов, принимает массив данных, которые должны быть добавлены в ответ ресурса:
return (new UserCollection(User::all()->load('roles'))) ->additional(['meta' => [ 'key' => 'value', ]]);
Как вы уже читали, ресурсы могут быть возвращены напрямую из маршрутов и контроллеров:
use App\Http\Resources\UserResource;use App\Models\User; Route::get('/user/{id}', function (string $id) { return new UserResource(User::findOrFail($id));});
Однако иногда вам может потребоваться настроить исходящий HTTP-ответ перед его отправкой клиенту. Есть два способа сделать это. Во-первых, вы можете цеплять метод response
к ресурсу. Этот метод вернет экземпляр Illuminate\Http\JsonResponse
, предоставляя полный контроль над заголовками ответа:
use App\Http\Resources\UserResource;use App\Models\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\JsonResponse;use Illuminate\Http\Request;use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource{ /** * Преобразует ресурс в массив. * * @return array<string, mixed> */ public function toArray(Request $request): array { return [ 'id' => $this->id, ]; } /** * Настраивает исходящий ответ для ресурса. */ public function withResponse(Request $request, JsonResponse $response): void { $response->header('X-Value', 'True'); }}