Guzzle HTTPでカクテルAPIを叩いてguzzle(酒を暴飲)する

[エキサイト Advent Calendar 2019](https://qiita.com/advent-calendar/2019/excite) 24日目の記事です！

こんにちは！aharenchです！
今回は、PHPのHTTP Clientの「GuzzleHttp」について書きたいと思います。


## Guzzle HTTPとは

> Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.
> ・ Simple interface for building query strings, POST requests, streaming large uploads, streaming large downloads, using HTTP cookies, uploading JSON data, etc...
> ・ Can send both synchronous and asynchronous requests using the same interface.

[Guzzle Documentationより](http://docs.guzzlephp.org/en/stable/)

GuzzleHttpとはPHP HTTPクライアントです。
シンプルなインターフェイスで、POSTリクエストやJSONデータアップロードなど色々なことができます。
また、逐次実行と並行実行どちらも行うことができます。
ちなみに、guzzleとは英語で「(酒)を暴飲する」という意味があるそうです。

ということで！！！！！
今回は**Guzzle HTTPでお酒を暴飲(!?):sunglasses::cocktail:**しながら説明したいと思います。

## 『Bar探し と 支度』

Guzzle HTTPでお酒を暴飲するためには、お酒:cocktail:とGuzzle HTTP本体が必要ですね。
用意していきましょう！

### HTTPリクエスト先のAPI 

[カクテルAPI：TheCocktailDB](https://www.thecocktaildb.com/api.php)

今回はお酒として、上記のカクテルAPIを利用します。
このAPIでは、カクテルの材料やグラス、作り方の情報を取得することができます。

以下のURIで、idを条件にカクテルの詳細情報を取得できます。
> https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=11000

どうやらこのidは1番開始では無いらしく、11000番代からカクテルの情報があるようです。

### インストール
GuzzleHttpのインストールは、Composerを利用して行います。

```shell-session
# 1.プロジェクト作成
$ mkdir Bar && cd $_
# 2.composer インストール
$ curl -sS https://getcomposer.org/installer | php
# 3.guzzlehttpをインストール
$ php composer.phar require guzzlehttp/guzzle:~6.0
```

## 『酒を1杯頼む』

準備ができたので、さっそくカクテルAPIにGETリクエストを送ってみましょう！
今回は、素のPHPで書いてきます。

> :sunglasses::point_up:「マスター！おすすめを一杯！」

### GETリクエスト
```Drink.php
<?php

require "vendor/autoload.php";

use GuzzleHttp\Client;

$client = new Client([
    // クライアントのBaseURI
    'base_uri' => 'https://www.thecocktaildb.com',
]);

// 次のURIにリクエストを送る -> 'https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=11000'
$response = $client->request('GET', "api/json/v1/1/lookup.php?i=11000");

// データ整形
$json = json_decode($response->getBody(), true);
$drinks = $json['drinks'];

// 結果出力
foreach ($drinks as $drink) {
    print_r("I drank {$drink['strDrink']}.");
}
```
**実行結果**

```
I drank Manhattan.
```



> :yum:「Manhattan美味しい！」

## 『酒をいっぱい頼む』

酒を暴飲するのが今回のテーマなので、どんどん飲んでいきましょう。
> :raised_hand_with_fingers_splayed::sunglasses:「マスター！おすすめをいっぱいください！」

### 複数回のGETリクエスト(逐次処理)

```SerialDrinks.php

<?php

require "vendor/autoload.php";

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://www.thecocktaildb.com',
]);

// 計測開始
$timeStart = microtime(true);

$total = 10;
$drinkList = [];
for ($i = 0; $i < $total; $i++) {
    // GETリクエスト 逐次実行
    $id = 11000 + $i;
    $response = $client->request('GET', "api/json/v1/1/lookup.php?i={$id}");

    // データ整形
    $json = json_decode($response->getBody(), true);
    $drinks = $json['drinks'];
    foreach ($drinks as $drink) {
        $drinkList[] = $drink['strDrink'];
    }
}

// 結果出力
foreach ($drinkList as $drink) {
    print_r("I drank {$drink}.\n");
}

// 計測終了
$time = microtime(true) - $timeStart;
print_r("実行時間：{$time} 秒");
```

```
I drank Mojito.
I drank Old Fashioned.
I drank Long Island Tea.
I drank Negroni.
I drank Whiskey Sour.
I drank Dry Martini.
I drank Daiquiri.
I drank Margarita.
I drank Manhattan.
I drank Moscow Mule.
実行時間：4.882071018219 秒
```

お酒を1杯ずつ味わって飲むのもいいですが、10杯飲むのに約4秒かかってしまいました。
これでは、1時間に約9000杯しか飲めません。
これじゃ暴飲とは言えません。

なんと、GuzzleHttpは並行処理が可能です。
とりあえず、今回は1度に5杯飲んでみたいと思います。

> :cocktail::cocktail::cocktail::cocktail::cocktail::raised_hand_with_fingers_splayed::sunglasses:「マスター！おすすめを一気にください！！」


### 複数回のGETリクエスト(並行処理)
```ParallelsDrinks.php
<?php

require "vendor/autoload.php";

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

$client = new Client([
    'base_uri' => 'https://www.thecocktaildb.com',
]);

// 計測開始
$time_start = microtime(true);

// リクエスト処理定義
$requests = function ($total) {
    for ($i = 0; $i < $total; $i++) {
        $id = 11000 + $i;
        yield new Request('GET', "api/json/v1/1/lookup.php?i={$id}");
    }
};

// promiseオブジェクトを生成
$drinkList = [];
$pool = new Pool($client, $requests(10), [
    //並行数
    'concurrency' => 5,
    // リクエストが成功した場合の処理
    'fulfilled'   => function (Response $response) use (&$drinkList) {
        $json = json_decode($response->getBody(), true);
        $drinks = $json['drinks'];
        foreach ($drinks as $drink) {
            $drinkList[] = $drink['strDrink'];
        }
    },
    // リクエストが失敗した場合の処理
    'rejected'    => function (RequestException $reason) {
        print_r("例外：" . $reason->getMessage() . "\n");
    },
]);

// promiseオブジェクトを並行処理で実行
$promise = $pool->promise();

// 実行したリクエストの処理を待つ
$promise->wait();

// 結果出力
foreach ($drinkList as $drink) {
    print_r("I drank {$drink}.\n");
}

// 計測終了
$time = microtime(true) - $time_start;
echo "実行時間：{$time} 秒";
```

**実行結果**

```
I drank Whiskey Sour.
I drank Mojito.
I drank Old Fashioned.
I drank Long Island Tea.
I drank Negroni.
I drank Margarita.
I drank Manhattan.
I drank Dry Martini.
I drank Daiquiri.
I drank Moscow Mule.
実行時間：1.3625779151917 
```

約1秒で10杯飲むことができました。
これで1時間に約36000杯飲むことができます。
大分、酒を暴飲できたのではないでしょうか。

## まとめ

GuzzleHttpのGETリクエストの逐次処理と並行処理を紹介しました。
並行処理をしたことで4倍のスピードを出すことができました。

今回は説明しませんでしたが、
冒頭で説明したようにシンプルにHTTPリクエストを行えるので
外部APIを利用する際はGuzzleHttpを使うのをおすすめします！

> :christmas_tree::sunglasses::cocktail:「クリスマス・イブに乾杯！」


### 参考文献
+ [Guzzleで非同期の並列リクエストを行う](https://mike-neko.github.io/blog/guzzle/)
+ [今時のPHP HTTPクライアントのGuzzleを使ってみた](https://qiita.com/yousan/items/2a4d9eac82c77be8ba8b)
+ [Guzzle Promiseを使った非同期処理によるAPIコールの高速化](https://speakerdeck.com/suzuki/guzzle-promisewoshi-tuta-fei-tong-qi-chu-li-niyoruapikorufalsegao-su-hua)
+ [Guzzle Httpを試してみたよ](https://qiita.com/clustfe/items/f9ff2b12da7a501197f8)