こんにちは、かつコーチです。
前回はAPI Resourceで自社のAPIレスポンスを整形する方法を解説しました。
今回はその逆で、天気情報や決済サービス、外部の認証基盤など、他社が提供するAPIをLaravelから呼び出す方法を扱います。
Laravelには Http ファサードという便利な仕組みが用意されているので、基本の使い方からつまずきやすいポイントまで見ていきましょう。
Httpファサードとは?
cURLを直接書かなくていい理由
PHPで外部APIを呼び出す方法として、昔からcURL関数を直接使う方法があります。
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $token]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
動作はしますが、オプションの指定が煩雑で、エラーハンドリングも自分で丁寧に書く必要があります。
Laravelの Http ファサードは、内部でGuzzle HTTPクライアントを使いながら、この面倒な部分をシンプルなメソッドチェーンで扱えるようにしたラッパーです。
use Illuminate\Support\Facades\Http;
$response = Http::withToken($token)->get('https://api.example.com/users');
$data = $response->json();
同じ処理でも、コード量と可読性がまったく違うことが分かるはずです。
GETとPOSTの基本形
まずは基本のGETとPOSTのリクエストです。
use Illuminate\Support\Facades\Http;
// GETリクエスト(クエリパラメータ付き)
$response = Http::get('https://api.example.com/search', [
'keyword' => 'Laravel',
'limit' => 10,
]);
// POSTリクエスト(JSONボディ)
$response = Http::post('https://api.example.com/posts', [
'title' => '新しい投稿',
'body' => '本文です',
]);
get() の第2引数に渡した配列は自動的にクエリパラメータ(?keyword=Laravel&limit=10)として組み立てられます。
post() の場合は、デフォルトでJSON形式のリクエストボディとして送信されます。
レスポンスの扱い方
レスポンスから値を取り出す
Http::get() や Http::post() の戻り値は Illuminate\Http\Client\Response というオブジェクトで、いくつか便利なメソッドが用意されています。
$response = Http::get('https://api.example.com/users/1');
$response->json(); // レスポンスボディを配列として取得
$response->json('name'); // レスポンスボディの'name'キーだけ取得
$response->body(); // レスポンスボディを文字列として取得
$response->status(); // HTTPステータスコード(200など)
$response->successful(); // 2xx系ならtrue
$response->failed(); // 4xx・5xx系ならtrue
$response->ok(); // 200ならtrue
実際のコードでは、成功・失敗をチェックしてから値を取り出すのが基本の流れです。
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
$response = Http::get('https://api.example.com/users/1');
if ($response->successful()) {
$user = $response->json();
} else {
Log::warning('外部APIの取得に失敗しました', [
'status' => $response->status(),
'body' => $response->body(),
]);
$user = null;
}
認証ヘッダーとタイムアウトの指定
APIキーやトークンを使う認証や、タイムアウトの設定もメソッドチェーンで書けます。
$response = Http::withToken($apiToken)
->timeout(5)
->withHeaders([
'X-Client-Version' => '1.0',
])
->get('https://api.example.com/users');
withToken() は Authorization: Bearer {token} ヘッダーを自動で付けてくれるメソッドです。
timeout(5) は5秒以内にレスポンスが返ってこなければタイムアウトさせる設定で、外部APIの応答が遅いときに自分のアプリまで固まってしまうのを防ぐために必須の設定です。
つまずきやすいポイント:タイムアウト未設定と例外処理
タイムアウトを設定せず本番障害につながった話
私が実際に業務でヒヤッとしたのが、外部APIのタイムアウトを設定していなかったことによる障害です。
❌ Before:タイムアウトを設定せずに外部APIを呼び出す
public function fetchWeather(string $city): array
{
$response = Http::get('https://weather-api.example.com/forecast', [
'city' => $city,
]);
return $response->json();
}
普段は問題なく動いていたのですが、ある日外部の天気APIがサーバー障害でレスポンスをまったく返さない状態になりました。
タイムアウトを設定していなかったため、このAPIを呼んでいたリクエストがいつまでも終わらず、Webサーバーのプロセスが徐々に埋まっていき、最終的に自社サイト全体の応答が遅くなるという事態に発展しました。
外部サービスの障害が、タイムアウト未設定のせいで自社サービス全体の障害に「連鎖」してしまった、という苦い経験です。
✅ After:タイムアウトと例外処理を必ずセットで書く
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Log;
public function fetchWeather(string $city): array
{
try {
$response = Http::timeout(3)
->retry(2, 100)
->get('https://weather-api.example.com/forecast', [
'city' => $city,
]);
if ($response->failed()) {
Log::warning('天気APIの取得に失敗しました', [
'status' => $response->status(),
]);
return [];
}
return $response->json();
} catch (ConnectionException $e) {
Log::error('天気APIに接続できませんでした', [
'message' => $e->getMessage(),
]);
return [];
}
}
timeout(3) で最大3秒しか待たないようにし、retry(2, 100) で失敗時に100ミリ秒間隔で2回まで自動リトライするようにしました。
さらに ConnectionException(接続自体に失敗した場合の例外)を try-catch で捕まえ、外部APIが落ちていても自社サービス側は空配列を返して処理を継続できるようにしています。
外部APIを呼ぶコードには、「タイムアウト」「リトライ」「例外処理」の3点セットを必ず入れる、というのが、この一件以降に自分の中でルール化した習慣です。
throw()で例外を投げてまとめて処理する
個別に failed() をチェックする代わりに、throw() を使って例外をまとめて扱う書き方もあります。
use Illuminate\Http\Client\RequestException;
try {
$response = Http::timeout(3)
->get('https://api.example.com/users/1')
->throw(); // 4xx・5xx系のときにRequestExceptionを投げる
$user = $response->json();
} catch (RequestException $e) {
Log::error('APIエラー', ['status' => $e->response->status()]);
$user = null;
}
throw() を挟んでおくと、失敗時のチェック漏れを防ぎやすくなるため、呼び出し箇所が多いプロジェクトではこちらのスタイルを採用することもあります。
一歩先の使い方:並列リクエストとテスト時のモック
複数のAPIを並列で呼び出す
複数の外部APIを順番に呼ぶと、合計の待ち時間がそのまま加算されてしまいます。
Http::pool() を使うと、複数のリクエストを並列に送信でき、待ち時間を短縮できます。
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('weather')->get('https://weather-api.example.com/forecast'),
$pool->as('news')->get('https://news-api.example.com/latest'),
]);
$weather = $responses['weather']->json();
$news = $responses['news']->json();
天気情報とニュース情報のように、互いに依存関係がないAPI呼び出しがある場合は、pool() にまとめることで全体のレスポンス時間を大きく短縮できます。
テストでは外部APIを呼ばずにモックする
外部APIに依存するコードをテストする際、実際にAPIを呼び出してしまうとテストが不安定になったり、料金が発生したりする可能性があります。
Http::fake() を使うと、外部APIへの通信をテスト用のダミーレスポンスに差し替えられます。
// tests/Feature/WeatherServiceTest.php
namespace Tests\Feature;
use App\Services\WeatherService;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class WeatherServiceTest extends TestCase
{
public function test_天気情報を正しく取得できる(): void
{
Http::fake([
'weather-api.example.com/*' => Http::response([
'city' => '東京',
'temperature' => 28,
], 200),
]);
$weather = app(WeatherService::class)->fetch('東京');
$this->assertSame(28, $weather['temperature']);
}
}
Http::fake() を使えば、実際のネットワーク通信を発生させずに、成功・失敗の両パターンを含めたテストを安定して実行できます。
外部APIを呼ぶ処理を実装したら、正常系だけでなく失敗時のテストも合わせて書いておくと、前述のような本番障害の予防にもつながります。
まとめ
この記事のポイント
- Laravelの
Httpファサードを使うと、cURLを直接扱うより簡潔に外部APIを呼び出せる - レスポンスは
json()やsuccessful()などのメソッドで安全に扱える - タイムアウト・リトライ・例外処理の3点セットを外部API呼び出しには必ず組み込む
Http::pool()で複数のAPIを並列に呼び出し、待ち時間を短縮できるHttp::fake()を使えば、外部通信を発生させずにテストを書ける
次に読むべき記事
外部APIとの連携方法がわかったところで、次回はアプリケーション全体の速度を底上げする、パフォーマンスチューニングの基本を解説します。
→ 次の記事:Laravelのパフォーマンスチューニング入門