Идёт обновление сайта. Несколько дней возможны сбои в оформлении и переводах. Документация работает — если страница выглядит сломанной, обновите её позже.

Документация
L Laravel L intervention/image
Войти
Главная Laravel 10.x Eloquent: API-ресурсы

Eloquent: API-ресурсы

10.x 7 мар 2026 г.

#Введение

При создании 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.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ]
}

Если вы хотите отключить обёртку внешнего ресурса, следует вызвать метод withoutWrapping на базовом классе Illuminate\Http\Resources\Json\JsonResource. Обычно этот метод вызывают в AppServiceProvider или другом service provider, который загружается при каждом запросе к приложению:

<?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.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ],
    "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.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ],
    "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 позволяет условно добавить атрибут в ответ ресурса:

/**
 * Transform the resource into an array.
 *
 * @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 также принимает замыкание в качестве второго аргумента, что позволяет вычислить результирующее значение только если указанное условие равно true:

'secret' => $this->when($request->user()->isAdmin(), function () {
    return 'secret-value';
}),

Метод whenHas можно использовать для включения атрибута, если он действительно присутствует в базовой модели:

'name' => $this->whenHas('name'),

Кроме того, метод whenNotNull позволяет включать атрибут в ответ ресурса, если атрибут не равен null:

'name' => $this->whenNotNull($this->name),

#Объединение условных атрибутов

Иногда может понадобиться включать несколько атрибутов в ответ ресурса только при выполнении одного и того же условия. В этом случае используйте метод mergeWhen, чтобы добавлять эти атрибуты в ответ только тогда, когда указанное условие равно true:

/**
 * Преобразовать ресурс в массив.
 *
 * @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,
    ];
}

Если указанное условие равно false, эти атрибуты будут удалены из ответа ресурса до его отправки клиенту.

Внимание

Метод 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'),

#Условная информация о pivot

Помимо условного включения информации о связях, вы можете условно включать данные из промежуточных таблиц many-to-many связей с помощью метода whenPivotLoaded. Метод whenPivotLoaded принимает имя таблицы pivot в качестве первого аргумента. Второй аргумент — замыкание, которое возвращает значение, если информация pivot доступна в модели:

/**
 * Преобразовать ресурс в массив.
 *
 * @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');
    }
}