Guzzle
PHP向けのHTTPクライアントライブラリとWebサービスフレームワーク。PSR-7メッセージインターフェース準拠、非同期リクエスト、ミドルウェアシステム、MockHandlerによるテスト支援機能を提供。LaravelやSymfonyなど主要フレームワークで採用。
GitHub概要
guzzle/guzzle
Guzzle, an extensible PHP HTTP client
スター23,428
ウォッチ446
フォーク2,390
作成日:2011年2月28日
言語:PHP
ライセンス:MIT License
トピックス
curlguzzlehttp-clienthttpclientphppsr-7requestswebservices
スター履歴
データ取得日時: 2025/10/22 09:54
ライブラリ
Guzzle
概要
Guzzleは「PHP向けの包括的HTTPクライアントライブラリ」として開発された、PHPエコシステムで最も採用されているHTTPクライアントです。PSR(PHP Standard Recommendations)準拠、Promise対応による非同期処理、プラグインシステム、豊富な設定オプションを提供し、シンプルなAPIから企業レベルの複雑な要件まで対応。Symfony、Laravel、WordPress等の主要PHPフレームワークで標準採用され、PHP開発において事実上のHTTPクライアント標準として確立されています。
詳細
Guzzle 2025年版はPHPコミュニティの成熟と共に進化を続け、PHP 8.x対応、型安全性強化、パフォーマンス最適化を実現したエンタープライズグレードのHTTPクライアントです。並行リクエスト処理、ストリーミング、クッキー管理、プロキシサポート、詳細なロギング機能により、Webスクレイピング、API統合、マイクロサービス通信など多様な用途に対応。PSR-7、PSR-18準拠による標準化されたHTTPメッセージ処理と、豊富なミドルウェアエコシステムにより、PHPアプリケーションのHTTP通信基盤として重要な役割を担っています。
主な特徴
- PSR準拠: PSR-7(HTTPメッセージ)、PSR-18(HTTPクライアント)標準対応
- 並行処理: Promise ベースの非同期・並列リクエスト処理
- ミドルウェア: 豊富なミドルウェアエコシステムと拡張性
- ストリーミング: 大容量データの効率的なストリーミング処理
- プロキシ対応: HTTP、HTTPS、SOCKSプロキシの完全サポート
- フレームワーク統合: 主要PHPフレームワークとの優れた統合
メリット・デメリット
メリット
- PHP開発者コミュニティでの高い信頼性と豊富な実績
- PSR準拠による標準化されたHTTPメッセージ処理
- Promise対応の非同期・並列処理による高いパフォーマンス
- 豊富なミドルウェアエコシステムによる拡張性
- 詳細な設定オプションによる柔軟なカスタマイズ性
- Laravel、Symfony等主要フレームワークとの優れた統合
デメリット
- PHP専用で他言語環境では利用不可
- 豊富な機能により学習コストが高い
- 設定項目が多く初心者には複雑
- 依存関係が多くComposerによる管理が必須
- メモリ使用量が高く軽量アプリケーションには重い
- HTTP/2対応が限定的で最新仕様への対応が遅れ気味
参考ページ
書き方の例
インストールと基本セットアップ
# Composerを使用したGuzzleのインストール
composer require guzzlehttp/guzzle
# Guzzle Promise(非同期処理用)
composer require guzzlehttp/promises
# PSR-7 HTTP Message Implementation(推奨)
composer require guzzlehttp/psr7
# バージョン確認
composer show guzzlehttp/guzzle
<?php
// Composer オートローダーの読み込み
require_once 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise;
use Psr\Http\Message\ResponseInterface;
基本的なリクエスト(GET/POST/PUT/DELETE)
<?php
require_once 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class GuzzleBasicExample
{
private $client;
public function __construct()
{
// 基本的なクライアント設定
$this->client = new Client([
'base_uri' => 'https://api.example.com/',
'timeout' => 30.0,
'headers' => [
'User-Agent' => 'MyApp/1.0 (Guzzle PHP)',
'Accept' => 'application/json'
]
]);
}
// 基本的なGETリクエスト
public function basicGetRequest()
{
try {
$response = $this->client->request('GET', 'users', [
'headers' => [
'Authorization' => 'Bearer your-jwt-token'
]
]);
$statusCode = $response->getStatusCode();
$contentType = $response->getHeaderLine('content-type');
$body = $response->getBody()->getContents();
echo "ステータス: {$statusCode}\n";
echo "Content-Type: {$contentType}\n";
echo "レスポンス: {$body}\n";
// JSONレスポンスの解析
$data = json_decode($body, true);
return $data;
} catch (RequestException $e) {
echo "リクエストエラー: " . $e->getMessage() . "\n";
if ($e->hasResponse()) {
echo "HTTPステータス: " . $e->getResponse()->getStatusCode() . "\n";
}
}
}
// クエリパラメータ付きGETリクエスト
public function getWithQuery()
{
try {
$response = $this->client->request('GET', 'users', [
'query' => [
'page' => 1,
'limit' => 10,
'sort' => 'created_at',
'filter' => 'active'
],
'headers' => [
'Authorization' => 'Bearer your-jwt-token'
]
]);
$data = json_decode($response->getBody()->getContents(), true);
echo "取得ユーザー数: " . count($data) . "\n";
return $data;
} catch (RequestException $e) {
echo "検索エラー: " . $e->getMessage() . "\n";
return null;
}
}
// POSTリクエスト(JSON送信)
public function postJsonRequest()
{
try {
$userData = [
'name' => '田中太郎',
'email' => '[email protected]',
'age' => 30
];
$response = $this->client->request('POST', 'users', [
'json' => $userData, // 自動的にJSON形式で送信
'headers' => [
'Authorization' => 'Bearer your-jwt-token'
]
]);
if ($response->getStatusCode() === 201) {
$createdUser = json_decode($response->getBody()->getContents(), true);
echo "ユーザー作成成功: " . $createdUser['id'] . "\n";
return $createdUser;
}
} catch (RequestException $e) {
echo "POST エラー: " . $e->getMessage() . "\n";
return null;
}
}
// PUTリクエスト(更新)
public function putRequest()
{
try {
$updatedData = [
'name' => '田中次郎',
'email' => '[email protected]'
];
$response = $this->client->request('PUT', 'users/123', [
'json' => $updatedData,
'headers' => [
'Authorization' => 'Bearer your-jwt-token'
]
]);
if ($response->getStatusCode() === 200) {
echo "ユーザー更新成功\n";
return json_decode($response->getBody()->getContents(), true);
}
} catch (RequestException $e) {
echo "更新エラー: " . $e->getMessage() . "\n";
return null;
}
}
// DELETEリクエスト
public function deleteRequest()
{
try {
$response = $this->client->request('DELETE', 'users/123', [
'headers' => [
'Authorization' => 'Bearer your-jwt-token'
]
]);
if ($response->getStatusCode() === 204) {
echo "ユーザー削除成功\n";
return true;
}
} catch (RequestException $e) {
echo "削除エラー: " . $e->getMessage() . "\n";
return false;
}
}
// フォームデータの送信
public function submitFormData()
{
try {
$response = $this->client->request('POST', 'login', [
'form_params' => [
'username' => 'testuser',
'password' => 'secret123',
'remember' => 'true'
]
]);
$loginData = json_decode($response->getBody()->getContents(), true);
echo "ログイン成功: " . $loginData['token'] . "\n";
return $loginData;
} catch (RequestException $e) {
echo "ログインエラー: " . $e->getMessage() . "\n";
return null;
}
}
// レスポンス詳細の取得
public function detailedResponse()
{
try {
$response = $this->client->request('GET', 'status');
echo "ステータスコード: " . $response->getStatusCode() . "\n";
echo "理由句: " . $response->getReasonPhrase() . "\n";
echo "プロトコルバージョン: " . $response->getProtocolVersion() . "\n";
// ヘッダー情報の取得
foreach ($response->getHeaders() as $name => $values) {
echo "ヘッダー {$name}: " . implode(', ', $values) . "\n";
}
// レスポンスサイズ
echo "コンテンツ長: " . $response->getHeaderLine('Content-Length') . "\n";
} catch (RequestException $e) {
echo "詳細取得エラー: " . $e->getMessage() . "\n";
}
}
}
// 使用例
$example = new GuzzleBasicExample();
$example->basicGetRequest();
$example->getWithQuery();
$example->postJsonRequest();
高度な設定とカスタマイズ(ヘッダー、認証、タイムアウト等)
<?php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\MessageFormatter;
use Psr\Log\LogLevel;
class AdvancedGuzzleExample
{
// カスタム設定のクライアント作成
public function createCustomClient()
{
$stack = HandlerStack::create();
// ログミドルウェア
$stack->push(
Middleware::log(
new \Monolog\Logger('guzzle'),
new MessageFormatter('{method} {uri} - {code} {phrase}')
)
);
// カスタムタイムアウト設定
$client = new Client([
'base_uri' => 'https://api.example.com/',
'timeout' => 30,
'connect_timeout' => 10,
'read_timeout' => 20,
'handler' => $stack,
'headers' => [
'User-Agent' => 'MyApp/1.0 (Guzzle PHP)',
'Accept' => 'application/json',
'Accept-Language' => 'ja-JP,en-US;q=0.9'
],
'cookies' => true, // Cookie管理を有効化
'allow_redirects' => [
'max' => 5,
'strict' => false,
'referer' => true,
'protocols' => ['https', 'http']
]
]);
return $client;
}
// Basic認証
public function basicAuthentication()
{
$client = new Client();
try {
$response = $client->request('GET', 'https://api.example.com/protected', [
'auth' => ['username', 'password'] // Basic認証
]);
echo "Basic認証成功: " . $response->getStatusCode() . "\n";
return json_decode($response->getBody()->getContents(), true);
} catch (RequestException $e) {
echo "認証エラー: " . $e->getMessage() . "\n";
return null;
}
}
// Digest認証
public function digestAuthentication()
{
$client = new Client();
try {
$response = $client->request('GET', 'https://api.example.com/digest-auth', [
'auth' => ['username', 'password', 'digest'] // Digest認証
]);
echo "Digest認証成功: " . $response->getStatusCode() . "\n";
return json_decode($response->getBody()->getContents(), true);
} catch (RequestException $e) {
echo "Digest認証エラー: " . $e->getMessage() . "\n";
return null;
}
}
// プロキシ設定
public function proxyConfiguration()
{
$client = new Client([
'proxy' => [
'http' => 'tcp://proxy.example.com:8080',
'https' => 'tcp://proxy.example.com:8080',
'no' => ['.example.com', 'localhost'] // プロキシを使わないホスト
]
]);
// 認証付きプロキシ
$clientWithAuth = new Client([
'proxy' => 'tcp://user:[email protected]:8080'
]);
try {
$response = $client->request('GET', 'https://api.example.com/data');
echo "プロキシ経由成功: " . $response->getStatusCode() . "\n";
} catch (RequestException $e) {
echo "プロキシエラー: " . $e->getMessage() . "\n";
}
}
// SSL/TLS設定
public function sslConfiguration()
{
$client = new Client([
'verify' => '/path/to/ca-bundle.crt', // CA証明書の指定
// 'verify' => false, // SSL検証を無効化(開発時のみ)
'ssl_key' => '/path/to/client-private-key.pem',
'cert' => ['/path/to/client-certificate.pem', 'password'],
'version' => CURL_HTTP_VERSION_2_0 // HTTP/2強制
]);
try {
$response = $client->request('GET', 'https://secure-api.example.com/data');
echo "SSL通信成功: " . $response->getStatusCode() . "\n";
} catch (RequestException $e) {
echo "SSL エラー: " . $e->getMessage() . "\n";
}
}
// Cookie管理
public function cookieHandling()
{
$jar = new \GuzzleHttp\Cookie\CookieJar();
$client = new Client(['cookies' => $jar]);
try {
// 最初のリクエストでCookieを設定
$response1 = $client->request('POST', 'https://api.example.com/login', [
'form_params' => [
'username' => 'user',
'password' => 'pass'
]
]);
echo "ログイン成功: " . $response1->getStatusCode() . "\n";
// 二回目のリクエストで自動的にCookieが送信される
$response2 = $client->request('GET', 'https://api.example.com/profile');
echo "プロフィール取得: " . $response2->getStatusCode() . "\n";
// Cookieの詳細確認
foreach ($jar as $cookie) {
echo "Cookie: " . $cookie->getName() . "=" . $cookie->getValue() . "\n";
}
} catch (RequestException $e) {
echo "Cookie処理エラー: " . $e->getMessage() . "\n";
}
}
// 条件付きリクエスト(ETag、Last-Modified)
public function conditionalRequests()
{
$client = new Client();
try {
// 最初のリクエストでETagを取得
$response1 = $client->request('GET', 'https://api.example.com/data');
$etag = $response1->getHeaderLine('ETag');
$lastModified = $response1->getHeaderLine('Last-Modified');
echo "ETag: {$etag}\n";
echo "Last-Modified: {$lastModified}\n";
// 条件付きリクエスト
$response2 = $client->request('GET', 'https://api.example.com/data', [
'headers' => [
'If-None-Match' => $etag,
'If-Modified-Since' => $lastModified
]
]);
if ($response2->getStatusCode() === 304) {
echo "データは変更されていません(304 Not Modified)\n";
} else {
echo "新しいデータを取得: " . $response2->getStatusCode() . "\n";
}
} catch (RequestException $e) {
echo "条件付きリクエストエラー: " . $e->getMessage() . "\n";
}
}
// カスタムヘッダーとミドルウェア
public function customHeadersAndMiddleware()
{
$stack = HandlerStack::create();
// カスタムミドルウェア(リクエストIDの追加)
$addRequestId = Middleware::mapRequest(function ($request) {
return $request->withHeader('X-Request-ID', uniqid('req_', true));
});
// レスポンス時間計測ミドルウェア
$timer = Middleware::tap(function ($request) {
$this->startTime = microtime(true);
}, function ($request, $options, $response) {
$duration = microtime(true) - $this->startTime;
echo "リクエスト時間: " . round($duration * 1000, 2) . "ms\n";
});
$stack->push($addRequestId);
$stack->push($timer);
$client = new Client([
'handler' => $stack,
'headers' => [
'X-API-Version' => 'v2',
'X-Client-Name' => 'PHP-Guzzle-Client'
]
]);
try {
$response = $client->request('GET', 'https://api.example.com/timed-endpoint');
echo "カスタムヘッダー送信成功: " . $response->getStatusCode() . "\n";
} catch (RequestException $e) {
echo "ミドルウェアエラー: " . $e->getMessage() . "\n";
}
}
}
// 使用例
$advanced = new AdvancedGuzzleExample();
$customClient = $advanced->createCustomClient();
$advanced->basicAuthentication();
$advanced->cookieHandling();
エラーハンドリングとリトライ機能
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class GuzzleErrorHandlingExample
{
private $client;
public function __construct()
{
$this->client = $this->createRobustClient();
}
// 堅牢なクライアントの作成
private function createRobustClient()
{
$stack = HandlerStack::create();
// リトライミドルウェア
$stack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
return new Client([
'handler' => $stack,
'timeout' => 30,
'connect_timeout' => 10
]);
}
// リトライ判定ロジック
private function retryDecider()
{
return function (
$retries,
RequestInterface $request,
ResponseInterface $response = null,
RequestException $exception = null
) {
// 最大3回まで
if ($retries >= 3) {
return false;
}
// 接続エラーの場合はリトライ
if ($exception instanceof ConnectException) {
return true;
}
// レスポンスがある場合
if ($response) {
$status = $response->getStatusCode();
// 5xxエラーまたは特定の4xxエラーでリトライ
if ($status >= 500 || $status === 429 || $status === 408) {
return true;
}
}
return false;
};
}
// リトライ遅延設定
private function retryDelay()
{
return function ($numberOfRetries) {
// 指数バックオフ(1秒、2秒、4秒)
return 1000 * pow(2, $numberOfRetries);
};
}
// 包括的なエラーハンドリング
public function comprehensiveErrorHandling()
{
try {
$response = $this->client->request('GET', 'https://api.example.com/may-fail');
$data = json_decode($response->getBody()->getContents(), true);
echo "リクエスト成功: " . count($data) . "件のデータ\n";
return $data;
} catch (ClientException $e) {
// 4xxエラー(クライアントエラー)
$response = $e->getResponse();
$statusCode = $response->getStatusCode();
$errorBody = $response->getBody()->getContents();
echo "クライアントエラー: {$statusCode}\n";
switch ($statusCode) {
case 400:
echo "不正なリクエスト: パラメータを確認してください\n";
break;
case 401:
echo "認証エラー: トークンが無効または期限切れです\n";
$this->refreshToken();
break;
case 403:
echo "アクセス拒否: 権限を確認してください\n";
break;
case 404:
echo "リソース未発見: URLを確認してください\n";
break;
case 429:
$retryAfter = $response->getHeaderLine('Retry-After');
echo "レート制限: {$retryAfter}秒後に再試行してください\n";
break;
default:
echo "エラー詳細: {$errorBody}\n";
}
} catch (ServerException $e) {
// 5xxエラー(サーバーエラー)
$response = $e->getResponse();
$statusCode = $response->getStatusCode();
echo "サーバーエラー: {$statusCode}\n";
switch ($statusCode) {
case 500:
echo "内部サーバーエラー: しばらく時間をおいて再試行してください\n";
break;
case 502:
echo "不正なゲートウェイ: サーバーが一時的に利用不可です\n";
break;
case 503:
echo "サービス利用不可: サーバーがメンテナンス中です\n";
break;
case 504:
echo "ゲートウェイタイムアウト: サーバーの応答が遅すぎます\n";
break;
}
} catch (ConnectException $e) {
// 接続エラー
echo "接続エラー: " . $e->getMessage() . "\n";
echo "ネットワーク接続またはDNS設定を確認してください\n";
} catch (RequestException $e) {
// その他のリクエストエラー
echo "リクエストエラー: " . $e->getMessage() . "\n";
if ($e->hasResponse()) {
$response = $e->getResponse();
echo "HTTPステータス: " . $response->getStatusCode() . "\n";
echo "レスポンス: " . $response->getBody()->getContents() . "\n";
}
} catch (\Exception $e) {
// 予期しないエラー
echo "予期しないエラー: " . $e->getMessage() . "\n";
error_log("Guzzle unexpected error: " . $e->getTraceAsString());
}
return null;
}
// カスタムリトライ戦略
public function customRetryStrategy($url, $maxRetries = 3)
{
$attempt = 0;
$baseDelay = 1; // 秒
while ($attempt <= $maxRetries) {
try {
$response = $this->client->request('GET', $url, [
'timeout' => 10 + ($attempt * 5) // 徐々にタイムアウトを延長
]);
echo "試行 {$attempt} で成功: " . $response->getStatusCode() . "\n";
return json_decode($response->getBody()->getContents(), true);
} catch (RequestException $e) {
$attempt++;
if ($attempt > $maxRetries) {
echo "最大試行回数に達しました: " . $e->getMessage() . "\n";
throw $e;
}
// リトライ対象の判定
$shouldRetry = false;
if ($e instanceof ConnectException) {
$shouldRetry = true;
} elseif ($e->hasResponse()) {
$status = $e->getResponse()->getStatusCode();
$shouldRetry = ($status >= 500 || $status === 429 || $status === 408);
}
if (!$shouldRetry) {
echo "リトライ対象外エラー: " . $e->getMessage() . "\n";
throw $e;
}
$delay = $baseDelay * pow(2, $attempt - 1);
echo "試行 {$attempt} 失敗、{$delay}秒後に再試行: " . $e->getMessage() . "\n";
sleep($delay);
}
}
}
// 並列リクエストでの部分失敗処理
public function partialFailureHandling()
{
$promises = [
'users' => $this->client->getAsync('https://api.example.com/users'),
'posts' => $this->client->getAsync('https://api.example.com/posts'),
'comments' => $this->client->getAsync('https://api.example.com/comments'),
'unreliable' => $this->client->getAsync('https://api.example.com/unreliable')
];
$responses = \GuzzleHttp\Promise\settle($promises)->wait();
$successful = [];
$failed = [];
foreach ($responses as $key => $response) {
if ($response['state'] === 'fulfilled') {
$successful[$key] = json_decode(
$response['value']->getBody()->getContents(),
true
);
echo "成功: {$key}\n";
} else {
$failed[$key] = $response['reason']->getMessage();
echo "失敗: {$key} - " . $response['reason']->getMessage() . "\n";
}
}
echo "成功: " . count($successful) . ", 失敗: " . count($failed) . "\n";
return ['successful' => $successful, 'failed' => $failed];
}
// サーキットブレーカーパターン
public function circuitBreakerPattern()
{
static $failureCount = 0;
static $lastFailureTime = 0;
$failureThreshold = 5;
$timeout = 30; // 30秒
// サーキット開放状態のチェック
if ($failureCount >= $failureThreshold) {
if (time() - $lastFailureTime < $timeout) {
throw new \Exception("サーキットブレーカー開放中: しばらく時間をおいて再試行してください");
} else {
// タイムアウト経過後は半開状態にリセット
$failureCount = 0;
}
}
try {
$response = $this->client->request('GET', 'https://api.example.com/circuit-test');
// 成功時はカウンターをリセット
$failureCount = 0;
echo "サーキットブレーカー: 正常状態\n";
return json_decode($response->getBody()->getContents(), true);
} catch (RequestException $e) {
$failureCount++;
$lastFailureTime = time();
echo "サーキットブレーカー: 失敗カウント {$failureCount}/{$failureThreshold}\n";
if ($failureCount >= $failureThreshold) {
echo "サーキットブレーカー: 開放状態に移行\n";
}
throw $e;
}
}
// トークンリフレッシュ処理(例)
private function refreshToken()
{
try {
$response = $this->client->request('POST', 'https://api.example.com/auth/refresh', [
'json' => [
'refresh_token' => 'stored-refresh-token'
]
]);
$tokenData = json_decode($response->getBody()->getContents(), true);
// 新しいトークンを保存(実際の実装では適切な保存方法を使用)
echo "トークンリフレッシュ成功: " . $tokenData['access_token'] . "\n";
} catch (RequestException $e) {
echo "トークンリフレッシュ失敗: " . $e->getMessage() . "\n";
}
}
}
// 使用例
$errorHandler = new GuzzleErrorHandlingExample();
$errorHandler->comprehensiveErrorHandling();
$errorHandler->partialFailureHandling();
try {
$errorHandler->customRetryStrategy('https://api.example.com/unstable');
} catch (Exception $e) {
echo "最終的に失敗: " . $e->getMessage() . "\n";
}
並行処理と非同期リクエスト
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use GuzzleHttp\Exception\RequestException;
class GuzzleConcurrentExample
{
private $client;
public function __construct()
{
$this->client = new Client([
'timeout' => 30,
'connect_timeout' => 10
]);
}
// 並列リクエストの基本例
public function basicConcurrentRequests()
{
$promises = [
'users' => $this->client->getAsync('https://api.example.com/users'),
'posts' => $this->client->getAsync('https://api.example.com/posts'),
'comments' => $this->client->getAsync('https://api.example.com/comments'),
'categories' => $this->client->getAsync('https://api.example.com/categories')
];
try {
$startTime = microtime(true);
// 全ての並列リクエストを待機
$responses = Promise\settle($promises)->wait();
$endTime = microtime(true);
$duration = round(($endTime - $startTime) * 1000, 2);
echo "並列実行時間: {$duration}ms\n";
$results = [];
foreach ($responses as $key => $response) {
if ($response['state'] === 'fulfilled') {
$results[$key] = json_decode(
$response['value']->getBody()->getContents(),
true
);
echo "成功: {$key}\n";
} else {
echo "失敗: {$key} - " . $response['reason']->getMessage() . "\n";
}
}
return $results;
} catch (\Exception $e) {
echo "並列リクエストエラー: " . $e->getMessage() . "\n";
return [];
}
}
// ページネーション対応の並列データ取得
public function parallelPagination($baseUrl, $totalPages)
{
$promises = [];
// 全ページのリクエストを並列で準備
for ($page = 1; $page <= $totalPages; $page++) {
$promises["page_{$page}"] = $this->client->getAsync($baseUrl, [
'query' => [
'page' => $page,
'limit' => 20
]
]);
}
echo "並列ページ取得開始: {$totalPages}ページ\n";
$responses = Promise\settle($promises)->wait();
$allData = [];
$successCount = 0;
foreach ($responses as $key => $response) {
if ($response['state'] === 'fulfilled') {
$pageData = json_decode(
$response['value']->getBody()->getContents(),
true
);
if (isset($pageData['items'])) {
$allData = array_merge($allData, $pageData['items']);
$successCount++;
echo "ページ取得成功: {$key}\n";
}
} else {
echo "ページ取得失敗: {$key} - " . $response['reason']->getMessage() . "\n";
}
}
echo "並列取得完了: {$successCount}/{$totalPages}ページ成功、合計" . count($allData) . "件\n";
return $allData;
}
// 並行制限付きリクエスト
public function concurrencyLimitedRequests($urls, $maxConcurrency = 5)
{
$results = [];
$chunks = array_chunk($urls, $maxConcurrency);
foreach ($chunks as $chunkIndex => $chunk) {
echo "チャンク " . ($chunkIndex + 1) . " 処理開始: " . count($chunk) . "件\n";
$promises = [];
foreach ($chunk as $index => $url) {
$promises[$index] = $this->client->getAsync($url)
->then(
function ($response) use ($url) {
return [
'url' => $url,
'success' => true,
'data' => json_decode($response->getBody()->getContents(), true)
];
},
function ($exception) use ($url) {
return [
'url' => $url,
'success' => false,
'error' => $exception->getMessage()
];
}
);
}
$chunkResults = Promise\settle($promises)->wait();
foreach ($chunkResults as $result) {
if ($result['state'] === 'fulfilled') {
$results[] = $result['value'];
}
}
// チャンク間の待機(レート制限対応)
if ($chunkIndex < count($chunks) - 1) {
sleep(1);
}
}
$successCount = count(array_filter($results, function($r) { return $r['success']; }));
echo "制限付き並列処理完了: {$successCount}/" . count($results) . "件成功\n";
return $results;
}
// 非同期ストリーミング処理
public function asyncStreamingRequests()
{
$streamUrls = [
'https://api.example.com/large-dataset-1',
'https://api.example.com/large-dataset-2',
'https://api.example.com/large-dataset-3'
];
$promises = [];
foreach ($streamUrls as $index => $url) {
$promises["stream_{$index}"] = $this->client->getAsync($url, [
'stream' => true, // ストリーミングモード
'sink' => fopen("php://temp", 'r+') // 一時ストリーム
])->then(function ($response) use ($url, $index) {
echo "ストリーム {$index} 取得開始: {$url}\n";
$stream = $response->getBody();
$totalSize = 0;
$processedData = [];
// ストリームを chunk 毎に処理
while (!$stream->eof()) {
$chunk = $stream->read(8192);
$totalSize += strlen($chunk);
// チャンク毎の処理(例:JSONラインの解析)
$lines = explode("\n", $chunk);
foreach ($lines as $line) {
if (!empty(trim($line))) {
$processedData[] = json_decode($line, true);
}
}
}
echo "ストリーム {$index} 処理完了: {$totalSize}バイト、" . count($processedData) . "行\n";
return $processedData;
});
}
$results = Promise\settle($promises)->wait();
$allStreamData = [];
foreach ($results as $key => $result) {
if ($result['state'] === 'fulfilled') {
$allStreamData[$key] = $result['value'];
} else {
echo "ストリーム処理失敗: {$key} - " . $result['reason']->getMessage() . "\n";
}
}
return $allStreamData;
}
// 条件付き並列実行
public function conditionalConcurrentRequests()
{
// 最初にメタデータを取得
$metaPromise = $this->client->getAsync('https://api.example.com/meta');
$metaResponse = $metaPromise->wait();
$metaData = json_decode($metaResponse->getBody()->getContents(), true);
echo "メタデータ取得完了\n";
// メタデータに基づいて条件付きリクエスト
$conditionalPromises = [];
if ($metaData['has_users']) {
$conditionalPromises['users'] = $this->client->getAsync('https://api.example.com/users');
}
if ($metaData['has_posts']) {
$conditionalPromises['posts'] = $this->client->getAsync('https://api.example.com/posts');
}
if ($metaData['version'] >= 2.0) {
$conditionalPromises['advanced'] = $this->client->getAsync('https://api.example.com/advanced-features');
}
if (!empty($conditionalPromises)) {
echo "条件付きリクエスト実行: " . count($conditionalPromises) . "件\n";
$results = Promise\settle($conditionalPromises)->wait();
$finalData = [];
foreach ($results as $key => $result) {
if ($result['state'] === 'fulfilled') {
$finalData[$key] = json_decode(
$result['value']->getBody()->getContents(),
true
);
}
}
return $finalData;
}
return [];
}
// プログレッシブな並列取得(段階的フォールバック)
public function progressiveConcurrentFetch()
{
$primaryEndpoints = [
'https://primary-api.example.com/data',
'https://primary-api.example.com/users',
'https://primary-api.example.com/posts'
];
$backupEndpoints = [
'https://backup-api.example.com/data',
'https://backup-api.example.com/users',
'https://backup-api.example.com/posts'
];
// 第1段階:プライマリAPIで並列試行
echo "第1段階: プライマリAPI並列試行\n";
$primaryPromises = [];
foreach ($primaryEndpoints as $index => $url) {
$primaryPromises[$index] = $this->client->getAsync($url, ['timeout' => 5]);
}
$primaryResults = Promise\settle($primaryPromises)->wait();
$finalResults = [];
$failedIndices = [];
foreach ($primaryResults as $index => $result) {
if ($result['state'] === 'fulfilled') {
$finalResults[$index] = json_decode(
$result['value']->getBody()->getContents(),
true
);
echo "プライマリ成功: エンドポイント {$index}\n";
} else {
$failedIndices[] = $index;
echo "プライマリ失敗: エンドポイント {$index}\n";
}
}
// 第2段階:失敗したエンドポイントのみバックアップAPIで並列試行
if (!empty($failedIndices)) {
echo "第2段階: バックアップAPI並列試行(" . count($failedIndices) . "件)\n";
$backupPromises = [];
foreach ($failedIndices as $index) {
$backupPromises[$index] = $this->client->getAsync($backupEndpoints[$index], ['timeout' => 10]);
}
$backupResults = Promise\settle($backupPromises)->wait();
foreach ($backupResults as $index => $result) {
if ($result['state'] === 'fulfilled') {
$finalResults[$index] = json_decode(
$result['value']->getBody()->getContents(),
true
);
echo "バックアップ成功: エンドポイント {$index}\n";
} else {
echo "バックアップ失敗: エンドポイント {$index}\n";
}
}
}
echo "プログレッシブ取得完了: " . count($finalResults) . "/" . count($primaryEndpoints) . "件成功\n";
return $finalResults;
}
}
// 使用例
$concurrent = new GuzzleConcurrentExample();
echo "=== 基本並列リクエスト ===\n";
$concurrent->basicConcurrentRequests();
echo "\n=== 並列ページネーション ===\n";
$concurrent->parallelPagination('https://api.example.com/paginated-data', 5);
echo "\n=== 制限付き並列処理 ===\n";
$urls = [
'https://api.example.com/item/1',
'https://api.example.com/item/2',
'https://api.example.com/item/3',
'https://api.example.com/item/4',
'https://api.example.com/item/5'
];
$concurrent->concurrencyLimitedRequests($urls, 2);
フレームワーク統合と実用例
<?php
// Laravel統合例
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class ApiClient
{
private $client;
private $baseUrl;
private $apiKey;
public function __construct()
{
$this->baseUrl = config('api.base_url');
$this->apiKey = config('api.key');
$this->client = new Client([
'base_uri' => $this->baseUrl,
'timeout' => 30,
'headers' => [
'Authorization' => "Bearer {$this->apiKey}",
'Accept' => 'application/json',
'User-Agent' => 'Laravel-App/1.0'
]
]);
}
// キャッシュ付きGETリクエスト
public function getCachedData($endpoint, $cacheKey, $ttl = 300)
{
return Cache::remember($cacheKey, $ttl, function () use ($endpoint) {
try {
$response = $this->client->get($endpoint);
return json_decode($response->getBody()->getContents(), true);
} catch (RequestException $e) {
Log::error("API Request Failed: {$endpoint}", [
'error' => $e->getMessage(),
'status' => $e->getResponse() ? $e->getResponse()->getStatusCode() : null
]);
throw $e;
}
});
}
// バッチ処理用のユーザー作成
public function createUsers(array $usersData)
{
$promises = [];
foreach ($usersData as $index => $userData) {
$promises["user_{$index}"] = $this->client->postAsync('users', [
'json' => $userData
]);
}
$results = \GuzzleHttp\Promise\settle($promises)->wait();
$created = [];
$failed = [];
foreach ($results as $key => $result) {
if ($result['state'] === 'fulfilled') {
$created[$key] = json_decode(
$result['value']->getBody()->getContents(),
true
);
} else {
$failed[$key] = $result['reason']->getMessage();
}
}
Log::info("Batch User Creation", [
'created' => count($created),
'failed' => count($failed)
]);
return compact('created', 'failed');
}
}
// Symfony統合例
namespace App\Service;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class HttpClientService
{
private $client;
private $logger;
public function __construct(ParameterBagInterface $params, LoggerInterface $logger)
{
$this->logger = $logger;
$stack = HandlerStack::create();
// Symfonyロガーとの統合
$stack->push(
Middleware::log(
$logger,
new \GuzzleHttp\MessageFormatter('{method} {uri} - {code} {phrase}')
)
);
$this->client = new Client([
'base_uri' => $params->get('api.base_url'),
'timeout' => $params->get('api.timeout'),
'handler' => $stack
]);
}
public function apiCall($method, $endpoint, array $options = [])
{
try {
$this->logger->info("Making API call", [
'method' => $method,
'endpoint' => $endpoint
]);
$response = $this->client->request($method, $endpoint, $options);
return [
'success' => true,
'data' => json_decode($response->getBody()->getContents(), true),
'status' => $response->getStatusCode()
];
} catch (RequestException $e) {
$this->logger->error("API call failed", [
'method' => $method,
'endpoint' => $endpoint,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage(),
'status' => $e->getResponse() ? $e->getResponse()->getStatusCode() : null
];
}
}
}
// ファイルアップロード機能
class FileUploader
{
private $client;
public function __construct()
{
$this->client = new Client([
'timeout' => 300, // 5分(大きなファイル用)
'connect_timeout' => 30
]);
}
// 進捗付きファイルアップロード
public function uploadWithProgress($filePath, $uploadUrl, callable $progressCallback = null)
{
if (!file_exists($filePath)) {
throw new \InvalidArgumentException("ファイルが見つかりません: {$filePath}");
}
$fileSize = filesize($filePath);
$uploadedBytes = 0;
try {
$response = $this->client->post($uploadUrl, [
'multipart' => [
[
'name' => 'file',
'contents' => fopen($filePath, 'r'),
'filename' => basename($filePath)
],
[
'name' => 'metadata',
'contents' => json_encode([
'original_name' => basename($filePath),
'size' => $fileSize,
'upload_time' => date('Y-m-d H:i:s')
])
]
],
'progress' => function ($downloadTotal, $downloadedBytes, $uploadTotal, $uploadedBytes) use ($progressCallback) {
if ($progressCallback && $uploadTotal > 0) {
$progressCallback($uploadedBytes, $uploadTotal);
}
}
]);
$result = json_decode($response->getBody()->getContents(), true);
echo "アップロード完了: " . $result['file_id'] . "\n";
return $result;
} catch (RequestException $e) {
echo "アップロードエラー: " . $e->getMessage() . "\n";
throw $e;
}
}
// チャンク分割アップロード
public function chunkedUpload($filePath, $uploadUrl, $chunkSize = 1048576) // 1MB chunks
{
$fileSize = filesize($filePath);
$chunks = ceil($fileSize / $chunkSize);
echo "ファイルを{$chunks}個のチャンクに分割してアップロード\n";
$uploadId = uniqid('upload_', true);
$handle = fopen($filePath, 'rb');
try {
for ($chunkNumber = 0; $chunkNumber < $chunks; $chunkNumber++) {
$chunkData = fread($handle, $chunkSize);
$isLastChunk = ($chunkNumber === $chunks - 1);
$response = $this->client->post($uploadUrl, [
'multipart' => [
[
'name' => 'upload_id',
'contents' => $uploadId
],
[
'name' => 'chunk_number',
'contents' => $chunkNumber
],
[
'name' => 'total_chunks',
'contents' => $chunks
],
[
'name' => 'is_last_chunk',
'contents' => $isLastChunk ? 'true' : 'false'
],
[
'name' => 'chunk_data',
'contents' => $chunkData
]
]
]);
$chunkResult = json_decode($response->getBody()->getContents(), true);
echo "チャンク {$chunkNumber} アップロード完了\n";
if ($isLastChunk && isset($chunkResult['file_url'])) {
echo "全チャンクアップロード完了: " . $chunkResult['file_url'] . "\n";
return $chunkResult;
}
}
} finally {
fclose($handle);
}
}
}
// Webスクレイピング例
class WebScraper
{
private $client;
public function __construct()
{
$this->client = new Client([
'timeout' => 30,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (compatible; WebScraper/1.0)',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
],
'cookies' => true
]);
}
// 複数ページの並列スクレイピング
public function scrapeMultiplePages(array $urls)
{
$promises = [];
foreach ($urls as $index => $url) {
$promises["page_{$index}"] = $this->client->getAsync($url)
->then(function ($response) use ($url) {
$html = $response->getBody()->getContents();
// DOMDocument での HTML 解析
$dom = new \DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new \DOMXPath($dom);
// タイトルとリンクを抽出
$title = $xpath->query('//title')->item(0)?->textContent ?? '';
$links = [];
foreach ($xpath->query('//a[@href]') as $link) {
$links[] = [
'text' => trim($link->textContent),
'href' => $link->getAttribute('href')
];
}
return [
'url' => $url,
'title' => $title,
'links' => $links,
'content_length' => strlen($html)
];
});
}
$results = \GuzzleHttp\Promise\settle($promises)->wait();
$scrapedData = [];
foreach ($results as $key => $result) {
if ($result['state'] === 'fulfilled') {
$scrapedData[] = $result['value'];
echo "スクレイピング成功: " . $result['value']['url'] . "\n";
} else {
echo "スクレイピング失敗: " . $result['reason']->getMessage() . "\n";
}
}
return $scrapedData;
}
}
// 使用例
try {
// Laravel style usage
$apiClient = new ApiClient();
$userData = $apiClient->getCachedData('users/123', 'user_123', 600);
// File upload with progress
$uploader = new FileUploader();
$uploader->uploadWithProgress(
'/path/to/large-file.pdf',
'https://api.example.com/upload',
function ($uploaded, $total) {
$percent = round(($uploaded / $total) * 100, 2);
echo "進捗: {$percent}%\n";
}
);
// Web scraping
$scraper = new WebScraper();
$results = $scraper->scrapeMultiplePages([
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
]);
echo "スクレイピング完了: " . count($results) . "ページ\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}