LaravelMiddleCoding

Как реализовать 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.

Sources

Related topics