Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Laravel: HTTP-клієнт і інтеграції

20 питань · ~20 хв · Версія v3.0

Увійдіть, щоб продовжити

Запити й відповіді, таймаути й повтори, макроси й глобальні middleware, пакети запитів, підміни в тестах, вебхуки - питання всіх рівнів, від junior до lead.

За спробу
20
У пулі
41
Проходжень
0
Середній бал
-
Пройшли на 70%+
-

Питання для підготовки

9 питань

Circuit Breaker захищає від каскадних збоїв при зверненні до ненадійної залежності (зовнішнє API, що «лежить»). Якщо помилок забагато - «ланцюг розривається», і запити певний час відхиляються миттєво, не витрачаючи ресурси на марні спроби.

Стани:

  • Closed - усе працює, запити йдуть.
  • Open - поріг помилок перевищено; запити одразу падають (fail fast).
  • Half-Open - через таймаут пропускаються пробні запити; успіх → Closed, провал → знову Open.
// концептуально через Cache як лічильник збоїв
if (Cache::get('cb:payments') === 'open') {
    throw new ServiceUnavailableException;
}

У Laravel реалізують через лічильники в Redis/Cache або пакети-обгортки HTTP-клієнта. Часто поєднують із retry + backoff.

Http - обгортка над Guzzle з розумними значеннями за замовчуванням, але саме за замовчуванням і ховаються проблеми.

Базовий виклик:

$response = Http::withToken($token)
    ->timeout(5)
    ->retry(3, 200)
    ->get('https://api.example/vacancies', ['page' => 1]);

if ($response->failed()) {
    // ...
}

$data = $response->json();

Чотири речі, без яких у прод виходити не варто:

1. Таймаут. За замовчуванням запит може висіти 30 секунд, тримаючи PHP-воркер. Чужий сервіс, що «підвис», кладе ваш застосунок, а не свій. timeout(5) і connectTimeout(2) обовʼязкові.

2. Повтори з паузою. retry(3, 200) рятує від разових збоїв, але повторювати можна лише ідемпотентні запити: повтор POST про створення платежу створить його двічі.

3. Обробка помилки. Http не кидає виняток на 4xx/5xx - повертає відповідь із failed() === true. Код, що одразу робить ->json()['id'], отримає null і піде далі, ніби все гаразд. Або перевіряйте явно, або ставте ->throw().

4. Ізоляція від збоїв. Виклик чужого API під час обробки запиту робить вашу доступність залежною від чужої. Такі виклики належать у чергу, а на повторювані збої добре лягає circuit breaker.

У тестах - Http::fake(), щоб мережі не було зовсім:

Http::fake(['api.example/*' => Http::response(['id' => 1], 200)]);
Http::assertSent(fn ($request) => $request->url() === 'https://api.example/vacancies');

Докладніше в документації: HTTP-клієнт

Підмінити HTTP-клієнт Laravel фейковими відповідями, щоб тест не залежав від мережі й чужого сервісу.

it('imports exchange rates', function () {
    Http::fake([
        'api.rates.example/*' => Http::response(['usd' => 41.2], 200),
        '*' => Http::response(status: 500),
    ]);

    (new ImportRates)->handle();

    expect(Rate::latest()->value('usd'))->toBe(41.2);

    Http::assertSent(fn (Request $request) => str_contains($request->url(), 'api.rates.example')
        && $request->hasHeader('Authorization'));
});

Що варто знати:

  • Шаблони URL з * підмінюють групи запитів. Запит, що не збігся з жодним шаблоном, піде в мережу насправді.
  • Http::preventStrayRequests() забороняє такі запити: будь-який непідмінений кидатиме виняток. Його вмикають у TestCase чи Pest.php для всього набору.
  • Http::sequence() повертає різні відповіді на послідовні виклики - так перевіряють повтори після помилки.
  • Помилки теж тестують: таймаут, 429, 500 - саме там зазвичай ховаються баги обробки.

Межа фейку: він закріплює ваше уявлення про чужий API. Коли провайдер змінить формат, тести лишаться зеленими - тому корисні контрактні перевірки чи хоча б моніторинг справжніх відповідей у проді.

Докладніше в документації: HTTP-клієнт: тестування

Через фасад Http - обгортку над Guzzle з простішим API.

use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.github.token'))
    ->acceptJson()
    ->timeout(5)
    ->get('https://api.github.com/repos/laravel/framework', ['per_page' => 10]);

$stars = $response->json('stargazers_count');

Http::post('https://api.example.com/orders', ['sku' => 'A-1', 'qty' => 2]);   // тіло в JSON

Що повертає: об'єкт Response з методами:

  • json('key.nested'), collect(), object(), body() - дані;
  • status(), successful(), failed(), clientError(), serverError() - статус;
  • header('X-RateLimit-Remaining') - заголовки.

Корисне з першого дня:

  • дані POST за замовчуванням ідуть як JSON; для форми - asForm(), для файлів - attach();
  • timeout() - не чекати 30 секунд за замовчуванням;
  • ключі API - у config/services.php і .env, а не в коді.

Головна відмінність від «голого» Guzzle: на відповіді 4xx і 5xx клієнт не кидає винятків - статус перевіряють самі або викликають throw().

Докладніше в документації: Виконання запитів

Бо помилкова відповідь - теж відповідь: її можна прочитати, залогувати, обробити по-різному залежно від коду. Тому клієнт повертає Response і лишає рішення вам.

$response = Http::get($url);

if ($response->notFound()) {
    return null;                       // немає - нормальна ситуація
}

if ($response->serverError()) {
    Log::warning('API недоступне', ['status' => $response->status()]);
    throw new ServiceUnavailable;
}

Коли потрібен виняток - throw():

$data = Http::get($url)->throw()->json();     // RequestException на 4xx/5xx
Http::get($url)->throwIf(fn ($r) => $r->status() >= 500);
Http::get($url)->throwUnlessStatus(200);

Типова помилка новачка:

$user = Http::get($url)->json();   // на 500 тут буде тіло помилки, а не користувач
$user['name'];                     // і далі - дивна помилка в іншому місці

Окремий випадок - немає відповіді зовсім (таймаут, відмова з'єднання): тоді кидається ConnectionException завжди, бо читати нічого.

Правило: на кожен зовнішній виклик - або явна перевірка статусу, або throw().

Докладніше в документації: Обробка помилок

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.