【Laravel】PHPで非同期通信をする(Guzzleで簡単!)

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、最近では「クラウド」という言葉は技術者なら誰でも知っているものになりました。

初めてこの言葉を聞いたときは「クラウド → 雲 → 雨が降る → いや、それ不吉じゃない??」と思ったものですが😂、インターネットを介して必要な処理だけを行うというやり方は、市場規模の推移を見るとどれだけ人気か分かります。

そして、私の場合も例に漏れずAWSを活用する開発もご依頼いただくようになったのですが、その結果必要となってきたことがあります。

それが・・・・・・

(PHPの)非同期通信

です。

非同期通信というのは、シンプルに言うと「それを待ってなくてもOK」な通信のことで、これはJavaScriptaxiosなどでよく使われる言葉じゃないでしょうか(JavaScriptPHPの非同期はニュアンスがちょっと違いますが)

つまり、「ひとりでやるのはしんどいから、みんなで完了させよう!」という「One for all All for one」の精神ですね(笑)

メリットとしては、もちろん処理の高速化です。

例えば、2つのURLにアクセスする場合を考えてみましょう。
まずは、非同期ではない場合です。

  1. URL(その1)にアクセス
  2. 待ち
  3. 完了✨
  4. URL(その2)にアクセス
  5. 待ち
  6. 完了✨

と1つずつ「アクセス&待ち」を繰り返すため、時間がかかります。

しかし、非同期の場合は次のようになります。

  1. URL その1にアクセス
  2. 待たずにURL(その2)にアクセス
  3. すべてのアクセスが完了する
  4. 完了✨✨

どうでしょう。

よーいドン」でアクセスするので、「待ち」の部分が重なり、その分早く処理できるというわけですね。

そうです❗

この非同期通信が使うと、クラウドへのHTTPアクセスが重複しても、処理を早くすることができるというわけです。

ということで、今回はLaravel+Guzzleでこの非同期通信を実装してみましょう。

何かの参考になりましたら嬉しいです。😄✨

「ユーザーは待ってくれない、
が原則です!」

開発環境: Laravel 8.x、Guzzle 7.4

やりたいこと

今回実装する内容は、「複数あるURLに非同期通信する」です。

しかし、JavaScriptの非同期とは違ってPHPは1回のアクセスで完結するので、少し違ってきます。

例えば次の場合をみてください。

  • URL(その1): 処理に1秒かかる
  • URL(その2): 処理に4秒かかる
  • URL(その3): 処理に9秒かかる

この場合、(PHPの)非同期通信だと全部で9秒かかります。

つまり、一番長い秒数は最低でも必要になってくるというわけですね。(逆にひとつずつ待っていると、全14秒かかります)

これは、例えばAWSGoogle Cloud両方に画像をアップロードして物体検出するようなイメージです。

Guzzle をインストールする

Laravel 7.x以降はHTTP ClientとしてGuzzleを使うようになったため、初期状態でインストールされていると思います。

そのため、もしLaravel 6.x以前の方は以下のコマンドを実行してGuzzleをインストールしてください。

composer require guzzlehttp/guzzle

コントローラーをつくる

では、もうGuzzleを使って非同期通信するコードをつくっていきます。
以下のコマンドでコントローラーをつくってください。

php artisan make:controller AsyncController

すると、コントローラーが作成されるので中身を以下のようにします。

app/Http/Controllers/AsyncController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use GuzzleHttp\Promise;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Aws\Rekognition\RekognitionClient;

class AsyncController extends Controller
{
    public function request()
    {
        $start_time = microtime(true);
        dump('開始');

        $client = new Client();
        $promises = [];

        for($i = 1 ; $i <= 4; $i++) {

            $url = route('async.response', $i); // 4つのURLを作成
            $promises[] = $client->getAsync($url) // Promise(非同期通信のためのアクセス情報)をつくる
                ->then(
                    function($response) use($i, $start_time) { // アクセスに成功した時

                        $end_time = microtime(true) - $start_time;
                        dump('非同期通信の終了 その'. $i .': '. $end_time .' 秒');

                    },
                    function(RequestException $e) { // アクセスに失敗したとき

                        dump($e->getMessage());

                    }
                );

        }

        $responses = Promise\Utils::settle($promises)->wait();
        dump('終了:'. microtime(true) - $start_time .' 秒');
    }

    public function response($id) { // 重たい処理のテスト

        $id = intval($id);

        if($id === 4) {

            abort(404); // $id が 4 の場合だけ 404 エラーを返す

        }

        $seconds = pow($id, 2); // $id を2乗して秒数にする
        sleep($seconds);

    }
}

この中でやっているのは次のとおりです。

request()

この中でやっている手順は次のとおりです。

  1. URLをつくる
  2. Promise(非同期通信のためのアクセス情報)をつくり、配列へ貯めていく
  3. 貯まった Promise を settle() へセットし、非同期通信を開始
  4. アクセスが全て終わったら完了

なお、今回はコードが短いので、アクセスに失敗した場合の処理もつけています。実際には、$i4のときです。

つまり、then()には1つ目がアクセスに成功した時で、2つ目は失敗したときの処理を書くことになります。

response()

このメソッドは非同期通信のアクセス先で、受け取ったIDを2乗した秒数を待機することになります。

つまり、

  • $i が 1 の場合: 1 秒待つ
  • $i が 2 の場合: 4 秒待つ
  • $i が 3 の場合: 9 秒待つ

ことになります。

また、$i4のときの処理は先ほど説明したとおりです。

ルートをつくる

では、先ほどのコントローラーをルートにセットしましょう。

routes/web.php

// 省略

use App\Http\Controllers\AsyncController;

// 省略

Route::get('async/request', [AsyncController::class, 'request'])->name('async.request');
Route::get('async/response/{id}', [AsyncController::class, 'response'])->name('async.response');

作業はたったこれだけで完了です。(Guzzle様々ですね👍)

お疲れ様でした😄✨

ちなみに: AWSのパッケージには非同期型が用意されています

AWSが用意してくれているaws/aws-sdk-phpというPHP用パッケージには、いろいろなクラスがありますが、その中にはGuzzleの非同期通信に対応しているものもあります。

例えば、Amazon Rekognitionの場合、以下のようにdetectCustomLabelsAsync()のようなメソッドを使うとPromiseを取得することができます。

$client = new RekognitionClient([
    'region' => 'ap-northeast-1',
    'version' => 'latest'
]);
$promises[] = $client->detectCustomLabelsAsync([
    'Image' => [
        'Bytes' => $bytes,
    ],
    'ProjectVersionArn' => env('AWS_REKOGNITION_ARN')
]);

つまり、わざわざ自前でURLとパラメータを用意しなくてもいいというわけですね。さすがAWSです👍

テストしてみる

では実際にテストしてみましょう❗
ブラウザで「https://*****/async/request」にアクセスします。

すると・・・・・・

少しわかりにくですが、まとめると次のようになります。

【各URLが完了した時間】

  • 1つ目のURL: 開始から 約 1.09 秒で完了
  • 2つ目のURL: 開始から 約 4.08 秒で完了
  • 3つ目のURL: 開始から 約 9.95 秒で完了

【全てが完了した時間】

9.953秒で完了

つまり、ひとつずつ「アクセス&待つ」で実行するとしたら最低でも開始から15秒はかかる計算になりますが、非同期通信を使ったおかげで9.953秒で完了したということになります。

高速化、成功です ✨😄👍

企業様へのご提案

今回の非同期通信を使うと、AWSGoogle Cloudなどのクラウドへのアクセスが重複する場合でも最低限の時間で完了することができるようになります。

また、実行環境の影響でどうしても外部サーバーからデータを取得する必要がある場合や、いくつかの環境に処理を分散する場合にも有効です。

もしそういった高速化を実装されたい場合は、ぜひお問い合わせからご連絡ください。お待ちしております。😄✨

おわりに

ということで、今回はLaravel + Guzzleで非同期通信を実装してみました。

非同期通信」という言葉がちょっとこむつかしい響きですが、Guzzleを使えばとてもシンプルに実装できることが分かっていただけたと思います。

ちなみに、Guzzleはとてもよくできたパッケージなのでファイルのアップロードや、ヘッダーのセットなどもシンプルに実装することができます。

昔のことを考えると、開発者に感謝の気持ちで一杯ですね。
まさに「You saved my day!」です。

できたら、私のコードも世のためになれば嬉しいです。

ではでは〜❗

「ランチェスター戦略の本買いました。
オリラジあっちゃんの影響で😂」

開発のご依頼お待ちしております 😊✨ どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly