【Laravel】HTTPクライアント(Http Facade)の使い方|外部API呼び出し・モック・リトライ設定

Laravel の Http Facade を使うと、外部 API 呼び出しを短いチェーンで書き、テストでは Http::fake で差し替えられます。本記事では基本操作からリトライ・モックまで整理します。

細かな API は Laravel のメジャーバージョンで変わることがあります。手元の composer.jsonlaravel/framework に合わせ、Laravel 公式ドキュメントの HTTP Client 章を確認してください。

はじめに

決済 API や社内マイクロサービス、SaaS の Webhook 先など、Laravel アプリから外部 HTTP を呼ぶ場面は多いです。file_get_contents や curl をその都度書くと、タイムアウトや認証、JSON の扱いが場所ごとに異なり、テストも外部通信に依存しがちになります。

Http Facade を使うと、リクエストの組み立てとレスポンスの確認を Laravel 流の API で統一できます。テストでは Http::fake でスタブを返せるため、本番 API に依存しない検証もしやすくなります。

この記事では次の範囲を扱います。

  • 基本的な GET / POST とレスポンスの読み方
  • ヘッダー・認証・タイムアウトの指定
  • エラー時の throw() とログ
  • retry による自動リトライ
  • Http::fake を使ったテスト
  • 実務でハマりやすい失敗例

背景:Http Facade が担う役割

Laravel の HTTP Client は、PHP で広く使われる HTTP クライアント Guzzle をベースにしたラッパーです。HttpFacade の一種で、static 風に書きながら裏側では HTTP クライアントへ処理を委譲します。

Illuminate\\Support\\Facades\\Http から getpost などを呼び出すと、Illuminate\\Http\\Client\\Response が返ります。

やりたいこと主なメソッド
GET で JSON を取得Http::get(...)$response->json()
POST で JSON を送るHttp::post($url, $data)
認証ヘッダーを付けるHttp::withToken(...) / Http::withHeaders(...)
待ち時間を制限するHttp::timeout(秒)
失敗時に再試行するHttp::retry(回数, 待機ミリ秒)
テストで通信を差し替えるHttp::fake([...])

Guzzle を直接使う選択肢もありますが、Laravel アプリ内では Http Facade のほうがドキュメントやテスト支援と揃いやすいです。

実装と検証

基本的な GET / POST

まずは最小の GET リクエストです。

<?php

use Illuminate\\Support\\Facades\\Http;

$response = Http::get('<https://api.example.com/users/1>');

if ($response->successful()) {
    $name = $response->json('name');
}

POST で JSON を送る例です。post の第 2 引数は、デフォルトで application/json として送られます。

<?php

use Illuminate\\Support\\Facades\\Http;

$response = Http::post('<https://api.example.com/users>', [
    'name' => 'Taylor',
    'role' => 'Developer',
]);

if ($response->created()) {
    $userId = $response->json('id');
}

レスポンスは body()json()status()successful() などで確認できます。successful() はステータスコードが 200 番台かどうかを返します。

ヘッダーと Bearer トークン

API キーや Bearer トークンは、チェーンで指定します。

<?php

use Illuminate\\Support\\Facades\\Http;

$response = Http::withHeaders([
    'Accept' => 'application/json',
])->withToken($accessToken)->get('<https://api.example.com/me>');

withTokenAuthorization: Bearer ... ヘッダーを付与するショートカットです。acceptJson()Accept: application/json を付ける糖衣メソッドです。

複数の外部 API を呼ぶサービスでは、ベース URL と共通ヘッダーを Macro にまとめる方法もあります。Macro は Http::github() のように、あらかじめ設定済みのクライアントを再利用する仕組みです。

タイムアウト

公式ドキュメントによると、HTTP クライアントのデフォルトタイムアウトは 30 秒です。接続待ちの上限(connectTimeout)は 10 秒が既定です。外部 API の応答が遅い場合は、明示的に短く設定するとよいです。

<?php

use Illuminate\\Support\\Facades\\Http;

$response = Http::timeout(5)
    ->connectTimeout(2)
    ->get('<https://api.example.com/slow-endpoint>');

タイムアウトを超えると Illuminate\\Http\\Client\\ConnectionException がスローされます。

エラー時の扱い

Laravel の HTTP クライアントは、Guzzle と異なり 400 番台・500 番台でも自動的に例外を投げません。失敗を検知したいときは $response->successful() を確認するか、$response->throw() を呼びます。

<?php

use Illuminate\\Support\\Facades\\Http;
use Illuminate\\Support\\Facades\\Log;

$response = Http::post('<https://api.example.com/orders>', ['item_id' => 42]);

if ($response->failed()) {
    Log::warning('External API error', [
        'status' => $response->status(),
        'body' => $response->body(),
    ]);
}

// 失敗時は RequestException を投げたい場合
$data = Http::post('<https://api.example.com/orders>', ['item_id' => 42])
    ->throw()
    ->json();

throw() は 4xx / 5xx のとき RequestException を投げます。呼び出し側でステータスごとに分岐したい場合は、throw() を使わず successful()status() で判定します。

リトライ設定

一時的な 5xx や接続エラーに備えて、retry で自動再試行できます。第 1 引数は最大試行回数、第 2 引数は試行間の待機ミリ秒です。既定では接続失敗や 4xx / 5xx などがリトライ対象になります。

<?php

use Illuminate\\Support\\Facades\\Http;

$response = Http::retry(3, 100)
    ->withToken($token)
    ->post('<https://api.example.com/orders>', [
        'item_id' => 42,
        'quantity' => 1,
    ]);

第 3 引数に callable を渡すと、「どの失敗でリトライするか」を絞れます。401 Unauthorized のように再試行しても意味がないケースでは、ここで条件を絞ります。第 2 引数の $request は、これから送るリクエストオブジェクト(PendingRequest)です。

<?php

use Illuminate\\Http\\Client\\ConnectionException;
use Illuminate\\Http\\Client\\PendingRequest;
use Illuminate\\Support\\Facades\\Http;
use Throwable;

$response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
    return $exception instanceof ConnectionException;
})->get('<https://api.example.com/status>');

リトライ後も失敗した場合、既定では ConnectionExceptionRequestException がスローされます。例外を出さず最後のレスポンスを返したいときは、throw: false を指定できます。

<?php

use Illuminate\\Support\\Facades\\Http;

$response = Http::retry(3, 100, throw: false)->get('<https://api.example.com/status>');

接続エラー時は、throw: false でも ConnectionException が残る点に注意してください。

サービスクラスに閉じ込める例

ルートやコントローラに Http:: を直書きすると、テストや再利用がしづらくなります。外部 API 呼び出しは app/Services/ 配下の専用クラスにまとめると扱いやすいです。

トークンは config/services.php.env に置くのが一般的です。

// config/services.php
'example' => [
    'token' => env('EXAMPLE_API_TOKEN'),
],
# .env
EXAMPLE_API_TOKEN=your-token-here
<?php

namespace App\\Services;

use Illuminate\\Http\\Client\\Response;
use Illuminate\\Support\\Facades\\Http;

class ExampleApiClient
{
    public function fetchUser(int $id): Response
    {
        return Http::acceptJson()
            ->withToken(config('services.example.token'))
            ->timeout(5)
            ->retry(2, 200)
            ->get("<https://api.example.com/users/{$id}>")
            ->throw(); // 4xx/5xx 時は RequestException
    }
}

Laravel はコンストラクタ引数がないサービスクラスを自動解決するため、app(ExampleApiClient::class) で取得できます。

テスト:Http::fake で外部通信を差し替える

Testing 節では、Http::fake でスタブレスポンスを返せます。PHPUnit / Pest どちらでも同じ API を使えます。

<?php

namespace Tests\\Feature;

use App\\Services\\ExampleApiClient;
use Illuminate\\Http\\Client\\Request;
use Illuminate\\Support\\Facades\\Http;
use Tests\\TestCase;

class ExampleApiClientTest extends TestCase
{
    public function test_fetch_user_returns_stubbed_response(): void
    {
        Http::fake([
            'api.example.com/users/*' => Http::response([
                'id' => 1,
                'name' => 'Taylor',
            ], 200),
        ]);

        $response = app(ExampleApiClient::class)->fetchUser(1);

        $this->assertSame('Taylor', $response->json('name'));

        Http::assertSent(function (Request $request) {
            return str_contains($request->url(), 'api.example.com/users/1')
                && $request->hasHeader('Authorization');
        });
    }
}

テスト中に意図しない本番 API へ通信しないよう、Http::preventStrayRequests() を基底クラスで有効にする方法もあります。fake されていない URL へのリクエストは例外になります。

<?php

namespace Tests;

use Illuminate\\Foundation\\Testing\\TestCase as BaseTestCase;
use Illuminate\\Support\\Facades\\Http;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        Http::preventStrayRequests();
    }
}

実装時の注意点

fake した URL パターンの一致

Http::fake のキーは URL の部分一致です。本番とテストで URL が異なると fake は効きません。その場合、実際の通信が走ります。preventStrayRequests と合わせて確認すると安全です。

retry の回数と相手 API への負荷

retry(3, 100) は最大 3 回試行を意味します。相手 API のレート制限がある場合、テストで Http::assertSentCount(3) を使い、失敗時に想定回数だけ送っているか確認するとよいです。

失敗例:500 レスポンスを成功と誤認した

外部 API 連携を初めて実装したとき、Http::post の戻り値をそのまま json() だけ読んで処理を進めていました。相手 API が一時的に 500 を返しても例外は発生せず、json()null や空配列になり、後続処理で「データがない」扱いになったケースがあります。

原因は、Laravel が 4xx / 5xx で自動例外を投げない仕様を見落としていたことです。対処として、重要な呼び出しでは throw() または successful() による分岐を入れ、次のようにログにステータスコードとレスポンス本文を残すようにしました。

Log::warning('External API error', [
    'status' => $response->status(),
    'body' => $response->body(),
]);

学び:通信の「書き方」と「失敗の扱い」をセットで決める

Http Facade はリクエストを短く書ける入口です。同時に、失敗が静かに返る設計でもあるため、クライアントクラスごとに throw()successful() をチームで決めると本番とテストの両方が安定しやすくなります。外部 API クライアントを 1 クラスにまとめ、Http::fake でそのクラスをテストする構成が、実務では扱いやすいことが多いです。

まとめ

  • Http Facade は Guzzle ベースの HTTP クライアントで、GET / POST やヘッダー・認証をチェーンで指定できる。
  • 4xx / 5xx は既定では例外にならないため、successful()throw() で明示的に扱う。
  • retry で一時的な失敗への再試行を設定でき、条件は callable で絞れる。
  • テストでは Http::fakeassertSent で外部通信なしに検証できる。

次に試せること

まだ Http を使っていない場合は、ルートや Tinker から 1 エンドポイントだけ Http::get で叩き、dd($response->status()) で動作確認するとよいです。

  1. 既存の外部 API 呼び出しを 1 箇所選び、Http::timeoutthrow() を追加してエラー時のログを確認する。
  2. その呼び出しをサービスクラスに移し、上記の ExampleApiClientTest を参考に Feature テストを 1 本書く。
  3. 相手 API の SLA に合わせて retry の回数と待機時間を調整し、テストで Http::assertSentCount を使って想定回数だけ送っているか確認する。

Source