Как реализовать API resources в Laravel для трансформации моделей?
API Resource в Laravel — класс, трансформирующий Eloquent-модель в JSON через метод toArray(). Используйте whenLoaded() для безопасной сериализации связей без N+1 запросов.
API Resources в Laravel
API Resource — это слой трансформации между Eloquent-моделью и JSON-ответом. Вместо того чтобы сериализовать модель напрямую (что раскрывает все поля БД), вы явно описываете, какие данные и в каком формате уйдут клиенту. Создаётся командой php artisan make:resource UserResource.
Базовый Resource
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'role' => $this->whenLoaded('role', fn() => $this->role->name),
'created_at' => $this->created_at->toIso8601String(),
];
}
}
В контроллере достаточно вернуть new UserResource($user) или UserResource::collection($users) — Laravel автоматически устанавливает заголовок Content-Type: application/json и статус 200.
Resource Collection с метаданными
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
public $collects = UserResource::class;
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'meta' => [
'total' => $this->total(),
'version' => 'v2',
],
];
}
}
Условные поля и вложенные ресурсы
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'secret' => $this->when($request->user()?->isAdmin(), $this->secret_key),
'posts' => PostResource::collection($this->whenLoaded('posts')),
'company' => new CompanyResource($this->whenLoaded('company')),
];
}
Метод whenLoaded() критически важен: если связь не была eager-loaded, поле просто исключается из ответа, а не провоцирует N+1 запрос. Метод when() позволяет включать поля только при выполнении условия — например, для admin-only полей.
Добавление заголовков и обёртки
// Убрать обёртку 'data' глобально:
JsonResource::withoutWrapping();
// Или добавить кастомный заголовок для конкретного ресурса:
public function withResponse(Request $request, $response): void
{
$response->header('X-Resource-Version', '2.0');
}
Подводные камни
- Вызов
$this->resource->relationбезwhenLoaded()вызывает скрытый N+1 — Eloquent загрузит связь лениво для каждого элемента коллекции. JsonResource::withoutWrapping()— глобальная настройка; если вызвать её в одном ресурсе, она снимает обёртку у всех ресурсов в том же запросе.- При использовании
UserResource::collection($paginator)пагинационные ссылки (links,meta) добавляются автоматически — но только если передан объект Paginator, а не обычная коллекция. - Поле
$this->when(false, ...)полностью удаляется из JSON, а не превращается вnull— это нарушает контракт API, если клиент ожидает поле всегда. - Resource не валидирует входные данные и не защищает от утечки полей через
$this->resource— разработчик вручную контролирует whitelist полей вtoArray(). - Нельзя кэшировать Resource-объект между запросами: он привязан к
$requestи содержит ссылку на модель. - При сериализации Carbon-дат без явного вызова (
->toIso8601String()или->format()) формат определяется глобальной настройкойCarbon::serializeUsing(), что может дать неожиданный результат в разных окружениях.
Common mistakes
- Сводить api resources к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Laravel 13 поверх PHP: request проходит через HTTP kernel, middleware, routing, controller/action и response pipeline.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет api resources через конкретную точку lifecycle в Laravel.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.