Guzzleへの移行: PHP開発者が直接cURL関数を使うべきでない理由

## 導入

長い歴史を持つ[cURL関数](https://www.php.net/manual/ja/ref.curl.php#ref.curl)ですが、近年では[Guzzle](https://docs.guzzlephp.org/en/stable/)というHTTPクライアントライブラリが主流になっている気がします。
なので使えるならGuzzle使ったほうがいいんじゃないかな〜って話を書きたいと思います。

※ 外部へリクエストを送信する方法として`file_get_contents`,`file`などの関数も存在しますがこれらも使わないようにすると幸せになれます。

## cURL関数の欠点

cURL関数はHTTPリクエストを行うためのPHPの標準的な手段で、cURL拡張さえ入っていれば外部のライブラリを必要とすることなく動きます。
ただ設計自体がかなり前にされたものなので、ユニットテストや複雑な構文など色々と問題になってきます。
問題を洗い出してみましょう。

### 1. 複雑な構文と設定

cURL関数は多くのオプションと設定を抱えており、これらを正確に設定することは煩雑でエラーが発生しやすいです。
リクエストのカスタマイズが必要な場合、可読性や保守性が低下します。

特に[curl_setoptの定数](https://www.php.net/manual/ja/function.curl-setopt.php)はかなりの数存在しており、定数で制御するのは正直使いづらいです。

```php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // true を設定すると、curl_exec() の戻り値を 文字列で返します。通常はデータを直接出力します。
curl_setopt($ch, CURLOPT_HEADER, 0); // true を設定すると、 CURLOPT_HTTPPROXYTUNNEL を使って CONNECT リクエストが生成された場合に、 ユーザーのコールバック関数 CURLOPT_HEADERFUNCTION および CURLOPT_WRITEFUNCTION では proxy CONNECT のレスポンスヘッダを抑制します。
curl_setopt($ch, CURLOPT_CRLF, 0); // true を設定すると、転送時に Unix 形式の改行を CRLF 形式に変換します。
$result = curl_exec($ch);
curl_close($ch);
```


### 2. エラーハンドリングの制限

cURL関数はエラーハンドリングが限定されており、具体的なエラー情報やステータスコードを取得するのが手間になります。

```php
$result = curl_exec($ch);

$errno = curl_errno($ch);
$error = curl_error($ch);
$httpcode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($result === false) {
    throw new RuntimeException($errno, $error, $httpcode); // 例外を投げたい場合、毎回設定が必要
}

curl_close($ch);
```


### 3. テストの複雑さ

cURL関数を使用すると、外部のAPIリクエストをテストすることが複雑になります。
モックやスタブを作成して外部APIをシミュレートすることは難しく、**結合テストでしかクオリティを担保できない**ことがよくあります。

```php
// cURL関数を使用したテストの例
$ch = curl_init('https://example.com');
$result = curl_exec($ch);
// テストを行うためには外部APIへの実際のリクエストが必要
```


### 4. 柔軟性の不足

単純なHTTPリクエストの送信だけなら問題ありませんが、非同期リクエストや進行中のリクエストのキャンセルなど、ちょっと複雑なことをしようとすると大変なことになります。

## なぜGuzzleか？

https://docs.guzzlephp.org/en/stable/#

先程の問題ををGuzzleでは全て解決できます。

また[packagist](https://packagist.org/?query=http%20client%20library)で調べても圧倒的なスター数となっており、Laravelにも標準で組み込まれています。

Guzzle以外だと[symfony/http-client](https://symfony.com/doc/current/http_client.html#scoping-client)とかになるんですかね...?（使ったことはないですが）

### 1. 簡潔なAPIと設定

Guzzleは簡潔で直感的なAPIを提供し、HTTPリクエストの構築がよりシンプルになります。オプションの設定やヘッダーの追加は、より分かりやすい構文で行うことができます。

```php
// Guzzleを使ったGETリクエストの例
use GuzzleHttp\Client;

$client = new Client();
$response = $client->get('https://example.com');
```

### 2. エラーハンドリングの向上

GuzzleはHTTPクライアントとしてのエラーハンドリングを強化しており、詳細なエラー情報やステータスコードを取得しやすくなっています。これにより、デバッグやエラー処理がスムーズに行えます。

```php
// Guzzleを使ったエラーハンドリングの例
use GuzzleHttp\Exception\RequestException;

try {
    $response = $client->get('https://example.com');
} catch (RequestException $e) {
    // エラーレスポンスを処理
    $statusCode = $e->getResponse()->getStatusCode();
    $body = $e->getResponse()->getBody()->getContents();
}
```

### 3. テストの容易性

Guzzleはユニットテストができるような設計になっており、モックやスタブを活用した外部APIのテストが容易に行えます。これにより、実際のAPIリクエストを再現してテストが可能です。

```php
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([
    new Response(200, [], 'Mocked Response'),
]);

$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);

$response = $client->get('https://example.com');
```

### 4. 高い柔軟性

Guzzleは非同期リクエスト、進行中のリクエストのキャンセル、イベントハンドリングなど、高度な機能を提供しています。これにより、異なる要件に柔軟かつ効果的に対応できます。

よくありそうなケースだと...

- [リクエスト失敗時に自動でリトライをするように設定する](https://github.com/caseyamcl/guzzle_retry_middleware)
    -  HTTPステータスやコネクション接続の失敗など細かくリトライの条件と回数を決定できます。 
- [リクエスト毎に一定間隔を開ける （delayする）](https://docs.guzzlephp.org/en/stable/request-options.html#delay)
    - 依存関係を適切に管理すれば通信をする際のみdelayさせることも可能です。
- [タイムアウトの時間を設定する](https://docs.guzzlephp.org/en/stable/request-options.html#connect-timeout)


### 5. PSR-7に準拠して作成されていること

[PSR-7: HTTP message interfaces](https://www.php-fig.org/psr/psr-7/)に準拠して作成されており、PSRが提供する共通インターフェイスを利用して、異なるライブラリやフレームワークでもレスポンスやリクエストを再利用できます。

実際にレスポンスを別のライブラリで利用することは多くないと思いますが、最悪Guzzleがサポートされなくなった後でも、PSRが提供するインターフェイスに従ってライブラリを置き換えるなどの作業ができそうです。


## cURL関数からGuzzleへ移行するタイミング

現状composerでパッケージ管理出来ている状態であればすぐに移行することを推奨します。
Laravelの場合はデフォルトで入っているのですぐにGuzzleで開発することが可能です。

逆にcomposerが導入できていないwordpressなどのプロダクトだと下手に導入せずに、cURL関数を使い続けたほうが良いかもしれません。