Middle: питання на співбесіді з теми «API»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Ключ data. Ресурс, повернений з контролера, загортається в об'єкт з ключем data:
{ "data": { "id": 1, "name": "Olena" } }
Обгортка дає місце для метаданих поруч з даними і захищає від вразливості старих браузерів з JSON-масивом на верхньому рівні.
public static $wrap = 'user'; // власний ключ для цього ресурсу
JsonResource::withoutWrapping(); // вимкнути глобально (AppServiceProvider)
withoutWrapping() не діє на пагіновані відповіді: їм data потрібен, бо поруч ідуть links і meta.
Метадані верхнього рівня:
// у класі ресурсу - щоразу, коли ресурс є кореневим
public function with(Request $request): array
{
return ['meta' => ['api_version' => '2026-10']];
}
// разово, з контролера
return UserResource::make($user)->additional(['meta' => ['cached' => false]]);
with() додається лише для кореневого ресурсу, не для вкладених.
Колекції:
return UserResource::collection(User::paginate(20));
Пагінована колекція автоматично отримує links (first, last, prev, next) і meta (current_page, total...). Для простої колекції - лише data.
Власний клас колекції - коли самій колекції потрібна логіка:
final class UserCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'summary' => ['active' => $this->collection->where('active', true)->count()],
];
}
}
Керування HTTP-відповіддю:
return UserResource::make($user)
->response()
->setStatusCode(201)
->header('Location', route('users.show', $user));
Або в ресурсі - withResponse(Request $request, JsonResponse $response) для заголовків, що потрібні щоразу.
Типові помилки:
- подвійна обгортка:
toArray()повертає['data' => [...]]- отримаєтеdata.data; - ключі колекції: за замовчуванням вони перенумеровуються; щоб зберегти, -
public $preserveKeys = true; - формат для клієнтів - це контракт: вимкнення обгортки чи зміна
$wrapна робочому API ламає всіх клієнтів, тож такі рішення приймають до першого релізу.
Через умовні методи ресурсу - вони не роблять запитів самі, а лише перевіряють, що вже завантажено.
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'author' => new UserResource($this->whenLoaded('author')),
'comments_count' => $this->whenCounted('comments'),
'is_featured' => $this->when($request->user()?->isEditor(), $this->is_featured),
$this->mergeWhen($request->user()?->isAdmin(), [
'internal_notes' => $this->internal_notes,
]),
];
}
whenLoaded('author')- поле з'явиться, лише якщо контролер зробивwith('author');whenCounted('comments')- післяwithCount('comments');when($condition, $value)- поле зникає з відповіді зовсім, якщо умова хибна;mergeWhen()- кілька полів за однією умовою.
Навіщо так: контролер вирішує, що завантажити для конкретного ендпойнта, а ресурс просто відображає. Без whenLoaded звернення $this->author в ресурсі робить запит на кожен елемент колекції - класичне N+1.
Права й приховані поля: when() з перевіркою ролі не дає віддати внутрішні поля звичайному клієнту - корисніше, ніж $hidden у моделі, бо залежить від того, хто питає.
JsonApiResource формує відповіді за специфікацією JSON:API, тож структуру не треба писати вручну.
php artisan make:resource PostResource --json-api
class PostResource extends JsonApiResource
{
public $attributes = ['title', 'body', 'created_at'];
public $relationships = ['author', 'comments'];
}
Що ресурс робить сам:
- структура
dataзtype,id,attributes,relationships; includedдля запитаних зв'язків - кожен об'єкт один раз, навіть якщо на нього посилаються кілька записів;- розріджені набори полів:
?fields[posts]=title,created_at- лише потрібні атрибути; - включення:
?include=author- зв'язки потрапляють у відповідь лише на запит; - заголовок
Content-Type: application/vnd.api+json.
Що він не робить: не розбирає фільтри й сортування з запиту - для цього документація радить spatie/laravel-query-builder.
Коли обирати: коли клієнти (мобільні застосунки, сторонні інтеграції, фронтенд з бібліотекою під JSON:API) виграють від стандартного формату й керування полями. Для внутрішнього API одного фронтенду звичайні ресурси простіші.
Пам'ятати про N+1: include з запиту має відповідати жадібному завантаженню в контролері; includePreviouslyLoadedRelationships() віддає вже завантажені зв'язки й без параметра.