イントロダクション
Laravelは、Guzzle HTTPクライアントの周りに表現力豊かで最小限のAPIを提供し、他のWebアプリケーションと通信するための外部HTTPリクエストをすばやく作成できるようにします。LaravelによるGuzzleのラッパーは、最も一般的なユースケースと素晴らしい開発者エクスペリエンスに焦点を当てています。
使い始める前に、アプリケーションの依存関係としてGuzzleパッケージを確実にインストールしてください。デフォルトでLaravelはこの依存パッケージを自動的に含めます。もし、以前にパッケージを削除したことがある場合は、Composerを介して再度インストールしてください。
composer require guzzlehttp/guzzle
リクエストの作成
リクエストを行うには、Http
ファサードが提供するhead
、get
、post
、put
、patch
、delete
メソッドを使用します。まず、外部のURLに対して基本的なGET
リクエストを行う方法を見てみましょう。
use Illuminate\Support\Facades\Http;
$response = Http::get('http://example.com');
get
メソッドはIlluminate\Http\Client\Response
のインスタンスを返します。これは、レスポンスを調べるために使用できるさまざまなメソッドを提供します。
$response->body() : string;
$response->json($key = null, $default = null) : array|mixed;
$response->object() : object;
$response->collect($key = null) : Illuminate\Support\Collection;
$response->status() : int;
$response->successful() : bool;
$response->redirect(): bool;
$response->failed() : bool;
$response->clientError() : bool;
$response->header($header) : string;
$response->headers() : array;
Illuminate\Http\Client\Response
オブジェクトはPHPのArrayAccess
インターフェイスも実装しており、そのレスポンスのJSONレスポンスデータへ直接アクセスできます。
return Http::get('http://example.com/users/1')['name'];
上記レスポンスメソッドに加え、以下のメソッドにより、レスポンスが特定のステータスコードを持つか判断できます。
$response->ok() : bool; // 200 OK
$response->created() : bool; // 201 Created
$response->accepted() : bool; // 202 Accepted
$response->noContent() : bool; // 204 No Content
$response->movedPermanently() : bool; // 301 Moved Permanently
$response->found() : bool; // 302 Found
$response->badRequest() : bool; // 400 Bad Request
$response->unauthorized() : bool; // 401 Unauthorized
$response->paymentRequired() : bool; // 402 Payment Required
$response->forbidden() : bool; // 403 Forbidden
$response->notFound() : bool; // 404 Not Found
$response->requestTimeout() : bool; // 408 Request Timeout
$response->conflict() : bool; // 409 Conflict
$response->unprocessableEntity() : bool; // 422 Unprocessable Entity
$response->tooManyRequests() : bool; // 429 Too Many Requests
$response->serverError() : bool; // 500 Internal Server Error
URIテンプレート
HTTPクライアントは、URIテンプレート仕様を用いて、リクエストURLを構築することも可能です。URIテンプレートで展開できるURLパラメータを定義するには、withUrlParameters
メソッドを使用します。
Http::withUrlParameters([
'endpoint' => 'https://laravel.com',
'page' => 'docs',
'version' => '9.x',
'topic' => 'validation',
])->get('{+endpoint}/{page}/{version}/{topic}');
リクエストのダンプ
送信するリクエストインスタンスを送信して、スクリプトの実行を終了する前にダンプしたい場合は、リクエスト定義の先頭にdd
メソッドを追加できます。
return Http::dd()->get('http://example.com');
リクエストデータ
もちろん、POST
、PUT
、PATCH
リクエストを作成するときは、リクエストとともに追加のデータを送信するのが一般的であるため、これらのメソッドは2番目の引数としてデータの配列を受け入れます。デフォルトでデータはapplication/json
コンテンツタイプを使用して送信されます。
use Illuminate\Support\Facades\Http;
$response = Http::post('http://example.com/users', [
'name' => 'Steve',
'role' => 'Network Administrator',
]);
GETリクエストクエリパラメータ
GET
リクエストを行うときは、クエリ文字列をURLに直接追加するか、キー/値ペアの配列をget
メソッドの2番目の引数として渡せます。
$response = Http::get('http://example.com/users', [
'name' => 'Taylor',
'page' => 1,
]);
フォームURLエンコードされたリクエストの送信
application/x-www-form-urlencoded
コンテンツタイプを使用してデータを送信する場合は、リクエストを行う前にasForm
メソッドを呼び出す必要があります。
$response = Http::asForm()->post('http://example.com/users', [
'name' => 'Sara',
'role' => 'Privacy Consultant',
]);
素のリクエスト本文の送信
リクエストを行うときに素のリクエスト本文を指定する場合は、withBody
メソッドを使用できます。コンテンツタイプは、メソッドの2番目の引数を介して提供できます。
$response = Http::withBody(
base64_encode($photo), 'image/jpeg'
)->post('http://example.com/photo');
マルチパートリクエスト
ファイルをマルチパートリクエストとして送信する場合は、リクエストを行う前にattach
メソッドを呼び出す必要があります。このメソッドは、ファイルの名前とその内容を引数に取ります。必要に応じて、ファイルのファイル名と見なす3番目の引数を指定できます。
$response = Http::attach(
'attachment', file_get_contents('photo.jpg'), 'photo.jpg'
)->post('http://example.com/attachments');
ファイルの素の内容を渡す代わりに、ストリームリソースを渡すこともできます。
$photo = fopen('photo.jpg', 'r');
$response = Http::attach(
'attachment', $photo, 'photo.jpg'
)->post('http://example.com/attachments');
ヘッダ
ヘッダは、withHeaders
メソッドを使用してリクエストに追加できます。このwithHeaders
メソッドは、キー/値ペアの配列を引数に取ります。
$response = Http::withHeaders([
'X-First' => 'foo',
'X-Second' => 'bar'
])->post('http://example.com/users', [
'name' => 'Taylor',
]);
accept
メソッドを使って、アプリケーションがリクエストへのレスポンスとして期待するコンテンツタイプを指定できます。
$response = Http::accept('application/json')->get('http://example.com/users');
利便性のため、acceptJson
メソッドを使って、アプリケーションがリクエストへのレスポンスとしてapplication/json
コンテンツタイプを期待することを素早く指定できます。
$response = Http::acceptJson()->get('http://example.com/users');
認証
基本認証のログイン情報とダイジェスト認証ログイン情報は、それぞれwithBasicAuth
メソッドとwithDigestAuth
メソッドを使用して指定します。
// BASIC認証
$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(/* ... */);
// ダイジェスト認証
$response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(/* ... */);
Bearerトークン
リクエストのAuthorization
ヘッダにBearerトークンをすばやく追加したい場合は、withToken
メソッドを使用できます。
$response = Http::withToken('token')->post(/* ... */);
タイムアウト
timeout
メソッドを使用して、レスポンスを待機する最大秒数を指定できます。
$response = Http::timeout(3)->get(/* ... */);
指定したタイムアウトを超えると、Illuminate\Http\Client\ConnectionException
インスタンスを投げます。
サーバへの接続を試みる最長待ち秒数をconnectTimeout
メソッドで指定できます。
$response = Http::connectTimeout(3)->get(/* ... */);
再試行
クライアントまたはサーバのエラーが発生した場合に、HTTPクライアントがリクエストを自動的に再試行するようにしたい場合は、retry
メソッドを使用します。retry
メソッドは、リクエストを試行する最大回数とLaravelが試行の間に待機するミリ秒数を引数に取ります。
$response = Http::retry(3, 100)->post(/* ... */);
必要であれば、retry
メソッドに第3引数を渡せます。第3引数には、実際に再試行を行うかどうかを決定するCallableを指定します。例えば、最初のリクエストでConnectionException
が発生した場合にのみ、リクエストを再試行したいとしましょう。
$response = Http::retry(3, 100, function ($exception, $request) {
return $exception instanceof ConnectionException;
})->post(/* ... */);
リクエストの試行に失敗した場合、新しく試みる前にリクエストへ変更を加えたい場合があります。これを実現するには、retry
メソッドに渡すコールバックのrequest引数を変更します。例えば、最初の試行が認証エラーを返した場合、新しい認証トークンを使ってリクエストを再試行したいと思います。
$response = Http::withToken($this->getToken())->retry(2, 0, function ($exception, $request) {
if (! $exception instanceof RequestException || $exception->response->status() !== 401) {
return false;
}
$request->withToken($this->getNewToken());
return true;
})->post(/* ... */);
すべてのリクエストが失敗した場合、
Illuminate\Http\Client\RequestException
インスタンスを投げます。この動作を無効にする場合は、throw
引数へfalse
を指定してください。無効にすると、すべての再試行のあと、クライアントが最後に受信したレスポンスを返します。
$response = Http::retry(3, 100, throw: false)->post(/* ... */);
Warning!! 接続の問題ですべてのリクエストが失敗した場合は、
throw
引数をfalse
に設定していてもIlluminate\Http\Client\ConnectionException
が投げられます。
エラー処理
Guzzleのデフォルト動作とは異なり、LaravelのHTTPクライアントラッパーは、クライアントまたはサーバのエラー(サーバからの「400」および「500」レベルの応答)で例外を投げません。successful
、clientError
、serverError
メソッドを使用して、これらのエラーのいずれかが返されたかどうかを判定できます。
// ステータスコードが200以上300未満か判定
$response->successful();
// ステータスコードが400以上か判定
$response->failed();
// レスポンスに400レベルのステータスコードがあるかを判定
$response->clientError();
// レスポンスに500レベルのステータスコードがあるかを判定
$response->serverError();
// クライアントまたはサーバエラーが発生した場合、指定コールバックを即座に実行
$response->onError(callable $callback);
例外を投げる
あるレスポンスインスタンスのレスポンスステータスコードがクライアントまたはサーバのエラーを示している場合にIlluminate\Http\Client\RequestException
のインスタンスを投げたい場合場合は、throw
かthrowIf
メソッドを使用します。
$response = Http::post(/* ... */);
// クライアントまたはサーバのエラーが発生した場合は、例外を投げる
$response->throw();
// エラーが発生し、指定条件が真の場合は、例外を投げる
$response->throwIf($condition);
// エラーが発生し、指定クロージャの結果が真の場合は例外を投げる
$response->throwIf(fn ($response) => true);
// エラーが発生し、指定条件が偽の場合は、例外を投げる
$response->throwUnless($condition);
// エラーが発生し、指定クロージャの結果が偽の場合は例外を投げる
$response->throwUnless(fn ($response) => false);
// レスポンスが特定のステータスコードの場合は、例外を投げる
$response->throwIfStatus(403);
// レスポンスが特定のステータスコードでない場合は、例外を投げる
$response->throwUnlessStatus(200);
return $response['user']['id'];
Illuminate\Http\Client\RequestException
インスタンスにはパブリック$response
プロパティがあり、返ってきたレスポンスを検査できます。
throw
メソッドは、エラーが発生しなかった場合にレスポンスインスタンスを返すので、他の操作をthrow
メソッドにチェーンできます。
return Http::post(/* ... */)->throw()->json();
例外がなげられる前に追加のロジックを実行したい場合は、throw
メソッドにクロージャを渡せます。クロージャを呼び出した後に、例外を自動的に投げるため、クロージャ内から例外を再発行する必要はありません。
return Http::post(/* ... */)->throw(function ($response, $e) {
//
})->json();
Guzzleミドルウェア
LaravelのHTTPクライアントはGuzzleで動いているので、Guzzleミドルウェアを利用して、送信するリクエストの操作や受信したレスポンスの検査ができます。送信リクエストを操作するには、withMiddleware
メソッドとGuzzleのmapRequest
ミドルウェアファクトリを組み合わせて、Guzzleミドルウェアを登録します。
use GuzzleHttp\Middleware;
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\RequestInterface;
$response = Http::withMiddleware(
Middleware::mapRequest(function (RequestInterface $request) {
$request = $request->withHeader('X-Example', 'Value');
return $request;
})
)->get('http://example.com');
同様に、GuzzleのmapResponse
ミドルウェアファクトリと組み合わせてwithMiddleware
メソッドをを登録すれば、受信HTTPレスポンスを検査できます。
use GuzzleHttp\Middleware;
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;
$response = Http::withMiddleware(
Middleware::mapResponse(function (ResponseInterface $response) {
$header = $response->getHeader('X-Example');
// ...
return $response;
})
)->get('http://example.com');
Guzzleオプション
withOptions
メソッドを使用して、追加のGuzzleリクエストオプションを指定できます。withOptions
メソッドは、キー/値ペアの配列を引数に取ります。
$response = Http::withOptions([
'debug' => true,
])->get('http://example.com/users');
同時リクエスト
複数のHTTPリクエストを同時に実行したい場合があります。言い換えれば、複数のリクエストを順番に発行するのではなく、同時にディスパッチしたい状況です。これにより、低速なHTTP APIを操作する際のパフォーマンスが大幅に向上します。
さいわいに、pool
メソッドを使い、これを実現できます。pool
メソッドは、Illuminate\Http\Client\Pool
インスタンスを受け取るクロージャを引数に取り、簡単にリクエストプールにリクエストを追加してディスパッチできます。
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->get('http://localhost/first'),
$pool->get('http://localhost/second'),
$pool->get('http://localhost/third'),
]);
return $responses[0]->ok() &&
$responses[1]->ok() &&
$responses[2]->ok();
ご覧のように、各レスポンスインスタンスは、プールに追加した順でアクセスできます。必要に応じas
メソッドを使い、リクエストに名前を付けると、対応するレスポンスへ名前でアクセスできるようになります。
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('first')->get('http://localhost/first'),
$pool->as('second')->get('http://localhost/second'),
$pool->as('third')->get('http://localhost/third'),
]);
return $responses['first']->ok();
マクロ
LaravelのHTTPクライアントでは、「マクロ」を定義可能です。マクロは、アプリケーション全体でサービスとやり取りする際に、共通のリクエストパスやヘッダを設定するために、流暢で表現力のあるメカニズムとして機能します。利用するには、アプリケーションの
App\Providers\AppServiceProvider
クラスのboot
メソッド内で、マクロを定義します。
use Illuminate\Support\Facades\Http;
/**
* 全アプリケーションサービスの初期起動処理
*
* @return void
*/
public function boot()
{
Http::macro('github', function () {
return Http::withHeaders([
'X-Example' => 'example',
])->baseUrl('https://github.com');
});
}
マクロを設定したら、アプリケーションのどこからでもマクロを呼び出し、保留中のリクエストを指定した設定で作成できます。
$response = Http::github()->get('/');
テスト
Laravelの多くのサービスでは、テストを簡単かつ表現豊かに書くための機能を提供しており、HTTPクライアントも例外ではありません。Http
ファサードのfake
メソッドにより、リクエストが行われたときにスタブ/ダミーレスポンスを返すようにHTTPクライアントに指示できます。
レスポンスのfake
たとえば、リクエストごとに空の200
ステータスコードレスポンスを返すようにHTTPクライアントに指示するには、引数なしでfake
メソッドを呼びだしてください。
use Illuminate\Support\Facades\Http;
Http::fake();
$response = Http::post(/* ... */);
特定のURLのfake
もしくは、配列をfake
メソッドに渡すこともできます。配列のキーは、fakeしたいURLパターンとそれに関連するレスポンスを表す必要があります。*
文字はワイルドカード文字として使用できます。FakeしないURLに対して行うリクエストは、実際に実行されます。Http
ファサードのresponse
メソッドを使用して、これらのエンドポイントのスタブ/fakeのレスポンスを作成できます。
Http::fake([
// GitHubエンドポイントのJSONレスポンスをスタブ
'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),
// Googleエンドポイントの文字列レスポンスをスタブ
'google.com/*' => Http::response('Hello World', 200, $headers),
]);
一致しないすべてのURLをスタブするフォールバックURLパターンを指定する場合は、単一の*
文字を使用します。
Http::fake([
// GitHubエンドポイントのJSONレスポンスをスタブ
'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']),
// 他のすべてのエンドポイントの文字列をスタブ
'*' => Http::response('Hello World', 200, ['Headers']),
]);
fakeレスポンスの順番
場合によっては、単一のURLが特定の順序で一連のfakeレスポンスを返すように指定する必要があります。これは、Http::sequence
メソッドを使用してレスポンスを作成することで実現できます。
Http::fake([
// GitHubエンドポイントの一連のレスポンスをスタブ
'github.com/*' => Http::sequence()
->push('Hello World', 200)
->push(['foo' => 'bar'], 200)
->pushStatus(404),
]);
レスポンスシーケンス内のすべてのレスポンスが消費されると、以降のリクエストに対し、レスポンスシーケンスは例外を投げます。シーケンスが空になったときに返すデフォルトのレスポンスを指定する場合は、whenEmpty
メソッドを使用します。
Http::fake([
// GitHubエンドポイントの一連のレスポンスをスタブ
'github.com/*' => Http::sequence()
->push('Hello World', 200)
->push(['foo' => 'bar'], 200)
->whenEmpty(Http::response()),
]);
一連のレスポンスをfakeしたいが、fakeする必要がある特定のURLパターンを指定する必要がない場合は、Http::fakeSequence
メソッドを使用します。
Http::fakeSequence()
->push('Hello World', 200)
->whenEmpty(Http::response());
Fakeコールバック
特定のエンドポイントに対して返すレスポンスを決定するために、より複雑なロジックが必要な場合は、fake
メソッドにクロージャを渡すことができます。このクロージャはIlluminate\Http\Client\Request
インスタンスを受け取り、レスポンスインスタンスを返す必要があります。クロージャ内で、返すレスポンスのタイプを決定するために必要なロジックを実行できます。
use Illuminate\Http\Client\Request;
Http::fake(function (Request $request) {
return Http::response('Hello World', 200);
});
行き先がないリクエストの防止
HTTPクライアントから送信したすべてのリクエストを個々のテスト、またはテストスイート全体で確実にフェイクにしたい場合は、preventStrayRequests
メソッドをコールします。このメソッドを呼び出すと、対応するフェイクレスポンスがないリクエストは、実際にHTTPリクエストを行うのではなく、例外を投げるようになります。
use Illuminate\Support\Facades\Http;
Http::preventStrayRequests();
Http::fake([
'github.com/*' => Http::response('ok'),
]);
// "ok"レスポンスが返される
Http::get('https://github.com/laravel/framework');
// 例外が投げられる
Http::get('https://laravel.com');
レスポンスの検査
レスポンスをfakeする場合、アプリケーションが正しいデータまたはヘッダを送信していることを確認するために、クライアントが受信するリクエストを調べたい場合があります。これは、Http::fake
を呼び出した後にHttp::assertSent
メソッドを呼び出し実現します。
assertSent
メソッドは、Illuminate\Http\Client\Request
インスタンスを受け取るクロージャを引数に受け、リクエストがエクスペクテーションに一致するかを示す論理値を返す必要があります。テストに合格するには、指定するエクスペクテーションに一致する少なくとも1つのリクエストが発行される必要があります。
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
Http::fake();
Http::withHeaders([
'X-First' => 'foo',
])->post('http://example.com/users', [
'name' => 'Taylor',
'role' => 'Developer',
]);
Http::assertSent(function (Request $request) {
return $request->hasHeader('X-First', 'foo') &&
$request->url() == 'http://example.com/users' &&
$request['name'] == 'Taylor' &&
$request['role'] == 'Developer';
});
必要に応じて、assertNotSent
メソッドを使用して特定のリクエストが送信されないことを宣言できます。
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
Http::fake();
Http::post('http://example.com/users', [
'name' => 'Taylor',
'role' => 'Developer',
]);
Http::assertNotSent(function (Request $request) {
return $request->url() === 'http://example.com/posts';
});
テスト中にいくつのリクエストを「送信」"したかを宣言するため、assertSentCount
メソッドを使用できます。
Http::fake();
Http::assertSentCount(5);
または、assertNothingSent
メソッドを使用して、テスト中にリクエストが送信されないことを宣言することもできます。
Http::fake();
Http::assertNothingSent();
リクエスト/レスポンスの記録
すべてのリクエストと、それに対応するレスポンスを収集するために、recorded
メソッドが使用できます。recorded
メソッドは、Illuminate\Http\Client\Request
とIlluminate\Http\Client\Response
インスタンスを含む配列のコレクションを返します。
Http::fake([
'https://laravel.com' => Http::response(status: 500),
'https://nova.laravel.com/' => Http::response(),
]);
Http::get('https://laravel.com');
Http::get('https://nova.laravel.com/');
$recorded = Http::recorded();
[$request, $response] = $recorded[0];
さらに、recorded
メソッドは、Illuminate\Http\Client\Request
とIlluminate\Http\Client\Response
インスタンスを受け取るクロージャを引数に取り、エクスペクテーションに基づいてリクエストとレスポンスのペアをフィルターするために使用できます。
use Illuminate\Http\Client\Request;
use Illuminate\Http\Client\Response;
Http::fake([
'https://laravel.com' => Http::response(status: 500),
'https://nova.laravel.com/' => Http::response(),
]);
Http::get('https://laravel.com');
Http::get('https://nova.laravel.com/');
$recorded = Http::recorded(function (Request $request, Response $response) {
return $request->url() !== 'https://laravel.com' &&
$response->successful();
});
イベント
LaravelはHTTPリクエストを送信する過程で、3つのイベントを発行します。RequestSending
イベントはリクエストが送信される前に発生し、ResponseReceived
イベントは指定したリクエストに対するレスポンスを受け取った後に発行します。ConnectionFailed
イベントは、指定したリクエストに対するレスポンスを受信できなかった場合に発行します。
RequestSending
とConnectionFailed
イベントは両方とも、パブリックの$request
プロパティを含んでおり、これを使えばIlluminate\Http\Client\Request
インスタンスを調べられます。同様に、ResponseReceived
イベントは、$request
プロパティと$response
プロパティを含んでおり、Illuminate\Http\Client\Response
インスタンスの検査に使用できます。このイベントのイベントリスナは、App\Providers\EventServiceProvider
サービスプロバイダで登録します。
/**
* アプリケーションのイベントリスナマップ
*
* @var array
*/
protected $listen = [
'Illuminate\Http\Client\Events\RequestSending' => [
'App\Listeners\LogRequestSending',
],
'Illuminate\Http\Client\Events\ResponseReceived' => [
'App\Listeners\LogResponseReceived',
],
'Illuminate\Http\Client\Events\ConnectionFailed' => [
'App\Listeners\LogConnectionFailed',
],
];