Guzzle

PHP向けのHTTPクライアントライブラリとWebサービスフレームワーク。PSR-7メッセージインターフェース準拠、非同期リクエスト、ミドルウェアシステム、MockHandlerによるテスト支援機能を提供。LaravelやSymfonyなど主要フレームワークで採用。

HTTPクライアントPHP高機能並行処理ComposerPSR準拠

GitHub概要

guzzle/guzzle

Guzzle, an extensible PHP HTTP client

スター23,428
ウォッチ446
フォーク2,390
作成日:2011年2月28日
言語:PHP
ライセンス:MIT License

トピックス

curlguzzlehttp-clienthttpclientphppsr-7requestswebservices

スター履歴

guzzle/guzzle Star History
データ取得日時: 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";
}