laravel12.laravel_111
R e i - D r e a m
for Laravel
TOP > Laravel Ver.12 > その他中間モジュール(APIサンプル)
Guest
login

最終投稿日:2026年3月24日

サンプルAPI
サンプルAPIの概要
前回まででAPI『クライアント』『サービス』の説明をざっくりとしました。
それではクライアント側に関してもう少し詳しく、サンプルを交えて説明します。
実際にSNSのつぶやきを検索するAPIクライアントを作成してみましょう。
ただし、つぶやきと言っても『X(旧Twitter)』ではありません。
XでAPIをリクエストするには有料ブランに加入する必要があるため、敷居が上がってしまいました。。
今回サンプルで利用させて貰うSNSは『BuleSky』となります。
こちらは現状無料でAPIを利用できますので、ご興味がある方はアプリの登録などしてみてはいかがでしょうか。
API環境構築
プログラム中でAPIに関わる値を効率よく参照できる様、各種設定ファイルへ登録します。
.env

### API設定
API_TIMEOUT=30
BLUE_SKY_URL=https://bsky.social/xrpc/
BLUE_SKY_NAME=xxxx-xxxx.bsky.social
BLUE_SKY_PASS=xxxx-xxxx-xxxx-xxxx

『BlurSky』の場合、最低でも以下の情報が必要となり、設定ファイルへ登録します。
・APIへアクセスするURL(ここでは共通部分のみ登録します)
・アプリの名前
・アプリのパスワード
 ※ 今回はタイムアウト設定も追加しています
~/config/const.php

<?php
return [
    // API設定情報
    'api' => [
        'time_out' => (int)env('API_TIMEOUT', 10),
        'blue_sky' => [
            'url' => env('BLUE_SKY_URL', ''),
            'name' => env('BLUE_SKY_NAME', ''),
            'password' => env('BLUE_SKY_PASS', ''),
            'end_point' => [
                'getToken' => 'com.atproto.server.createSession',
                'getRefresh' => 'com.atproto.server.refreshSession',
                'getSearch' => 'app.bsky.feed.searchPosts',
            ],
        ],
    ],
];

『.env』の内容を「const.php」から取得できる様にします。
また、このファイルでエンドポイントである以下の情報を追加しています。
○ getToken           アクセストークン取得
○ getRefresh        トークンリフレッシュ実施
○ getSearch          SNS検索リクエスト
APIクライアント作成
それではAPIの心臓である『クライアント』を作成します。
作成にあたり、本APIクライアントの機能概要を軽く以下にまとめます。
○ API情報の最終的な格納場所はセッションとします。
○ セッション情報が存在しない場合、クライアント初期化のタイミングでトークンを取得する。
○ API実行に失敗した場合、自動的にトークンをリフレッシュしリトライする。
それでは『~/app/Services/ApiBlueSkyClient.php』に作成します。
ApiBlueSkyClient.php

<?php
namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Session;

class ApiBlueSkyClient {
    private bool $reTryAccessJwt = true;             // アクセストークン取得リトライフラグ
    private bool $reTryRefreshJwt = true;            // リフレッシュトークン取得リトライフラグ

    private int $exe_code = 500;                          // API実行結果コード
    private string $exe_message;                        // API実行結果メッセージ
    private bool $exe_res = false;                        // API実行結果

    private string $baseUrl;                                  // ベースURL
    private string $accessJwt;                              // アクセストークン
    private string $refreshJwt;                              // リフレッシュトークン
    private string $did;                                          // 自分のDID
    private string $time_out;                                 // 通信タイムアウト
    private string $app_name;                              // アプリ名
    private string $app_pass;                               // アプリパスワード

    // コンストラクタ
    public function __construct() {
        $this->baseUrl = config('const.api.blue_sky.url');
        $this->time_out = config('const.api.time_out');
        $this->app_name = config('const.api.blue_sky.name');
        $this->app_pass = config('const.api.blue_sky.password');
        // トークンがない場合は発行する
        if (empty(Session::get('blurSky_accessJwt', ''))) {
            $this->getToken();
        } else {
            // API情報の格納
            $this->setApiInfo(null, false);
            // API実行結果格納
            $this->setApiExeRes(true, 200, '初期化に成功しました。');
        }
    }

    // getter
    public function isExeRes() {
        return $this->exe_res;
    }
    public function getExeCode() {
        return $this->exe_code;
    }
    public function getExeMessage() {
        return $this->exe_message;
    }

    // API実行結果格納
    private function setApiExeRes(bool $res, int $code, string $msg) {
        $this->exe_res = $res;
        $this->exe_code = $code;
        $this->exe_message = $msg;
    }

    // API情報の格納
    private function setApiInfo($json, $con = true) {
        if ($con) {
            Session::put('blurSky_accessJwt', $json['accessJwt']);
            Session::put('blurSky_refreshJwt', $json['refreshJwt']);
            Session::get('blurSky_did', $json['did']);
        }
        $this->accessJwt = Session::get('blurSky_accessJwt', '');
        $this->refreshJwt = Session::get('blurSky_refreshJwt', '');
        $this->did = Session::get('blurSky_did', '');
    }

    // API実行
    private function client() {
        return Http::acceptJson()->baseUrl($this->baseUrl)->timeout($this->time_out)->throw();
    }

    // アクセストークン取得
    private function getToken() {
        // エンドポイント設定
        $end_point = config('const.api.blue_sky.end_point.getToken');
        // ヘッダー作成
        $headers = ['Content-Type' => 'application/json'];
        // POST情報作成
        $data = [
            'identifier' => $this->app_name,
            'password' => $this->app_pass,
        ];
        // APIリクエスト
        try {
            $api_res = $this->client()->withHeaders($headers)->post($end_point, $data);
            // 返却情報をセッションに格納
            $arrRes = $api_res->json();
            // API情報格納
            $this->setApiInfo($arrRes);
            // API実行結果格納
            $this->setApiExeRes(true, 200, '正常にアクセストークンを取得しました。');

            return true;
        } catch(RequestException $e) {
            // API実行結果格納
            $this->setApiExeRes(false, 500, 'アクセストークンの取得に失敗しました。');
        }

        return false;
    }

         // リフレッシュトークン処理
    private function getRefreshToken() {
        // エンドポイント設定
        $end_point = config('const.api.blue_sky.end_point.getRefresh');
        $ch = curl_init($this->baseUrl . $end_point);
        // cURL実行(LaravelではPOSTリクエストでNULLのPOSTデータは設定できない)
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->refreshJwt,
                'Content-Type: application/json',
            ],
        ]);
        $api_res = curl_exec($ch);
        $api_res = json_decode($api_res, true);
        $api_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        // 実行結果確認
        if ($api_code != 200) {
            // API実行結果格納
            $this->setApiExeRes(false, $api_code, $api_res['message']);

            return false;
        }
        // API実行結果格納
        $this->setApiExeRes(true, 200, 'リフレッシュトークン処理が正常に終了しました。');
        // API情報の格納
        $this->setApiInfo($api_res);

        return true;
    }

    // 文字列検索
    public function getSearch($word, $hash = true, $limit = 10) {
        // エンドポイント設定
        $end_point = config('const.api.blue_sky.end_point.getSearch');
        // キーワード
        $keyword = $hash ? '%23' . $word : $word;
        // 最大取得件数
        $limit = $limit;
        // POSTデータ作成
        $data['q'] = $keyword;
        $data['limit'] = $limit;
        // ヘッダー情報作成
        $headers = ['Authorization' => 'Bearer ' . $this->accessJwt, 'Content-Type' => 'application/json'];
        // APIリクエスト
        try {
            $api_res = $this->client()->withHeaders($headers)->get($end_point, ['q' => 'reidream', 'limit' => '10'])->throw();
            $api_res = $api_res->json();
            // API実行結果格納
            $this->setApiExeRes(true, 200, '正常に検索APIを実行しました(' . count($api_res) . ')。');
        } catch(RequestException $e) {
            // API実行結果格納
            $this->setApiExeRes(false, 500, '検索APIの処理に失敗しました。');
            // APIリトライ判断(リフレッシュトークン)
            if ($this->reTryRefreshJwt) {
                // リフレッシュトークン処理実施
                $this->reTryRefreshJwt = false;
                // リフレッシュトークン処理トライ
                if ($this->getRefreshToken()) {
                    // 文字列検索リトライ
                    $api_res = $this->getSearch($word, $hash, $limit);
                }
            }
            // APIリトライ判断(アクセストークン)
            if ($this->reTryAccessJwt) {
                // アクセストークン取得実施
                $this->reTryAccessJwt = false;
                // アクセストークン取得トライ
                if ($this->getToken()) {
                    // 文字列検索リトライ
                    $api_res = $this->getSearch($word, $hash, $limit);
                } else {
                    // API実行結果格納
                    $this->setApiExeRes(false, 500, '検索APIの処理(リトライ)に失敗しました。');
                    return [];
                }
            }
        }

        return $api_res;
    }
}

《機能説明》
○ コンストラクタ(function __construct)
設定ファイルに登録した以下値をプロパティへ代入します。
・APIのベースURL
・API通信タイムアウト値
・アプリ名
・アプリパスワード
セッションを確認しアクセストークンがない場合は、取得関数(getToken)をコールする。
既にセッションにアクセストークンが存在する場合は、プロパティセット関数(setApiInfo)をコールする。
○ API情報の格納関数(function setApiInfo)
取得したAPIレスポンス(配列へデコード済)を引数とし、セッションへ値を格納後プロパティに代入します。
既にセッション情報がある場合は、第①引数を《null》、第②引数を《false》とする。
○ アクセストークン取得関数(function getToken)
アクセストークンの取得に成功した場合は、API情報格納関数をコール後《true》を返却します。
アクセストークンの取得に失敗した場合は、任意の処理後《false》を返却します。
○ リフレッシュトークン処理関数(function getRefreshToken)
リフレッシュトークンをリクエストしますが《cURL》を利用した通信をしています。
これはLaravelの仕様上、空(null)のPOST値を渡せない事に起因します。
BlueSky側では例えPOSTデータが空であってもエラー判定し、明確な《null》を求める様です。
従って妥協案として《cURL》を使用しています。
リクエスト後の挙動は『アクセストークン取得』と同様となります。
○ 文字列検索関数(function getSearch)
本サンプルのメイン関数となります。
この処理(BlueSkyからつぶやきを取得)をしたいために、他の関数達が存在ます。
処理自体は『アクセストークン』を利用して、任意の文字列を検索するだけです。
ただしここで、このクラスの構造上以下の懸念があります。
・アクセストークンの有効期間が超過している
・リフレッシュトークンの有効期間が超過している
この場合当然リクエストはエラー扱いとなります。
そこでサンプルではリクエストに失敗した場合、1度だけリフレッシュ処理を実行します。
成功した場合は、再度文字列検索リクエストを実施します。
更に運が悪い事に、リフレッシュトークンが古すぎてリフレッシュ処理が失敗する可能性もあります。
その場合、1度だけアクセストークン取得をトライします。
成功した場合は、再度文字列検索リクエストを実施します。
ポイント

マナーとして『アクセストークン』取得、『リフレッシュ』処理の乱発は避けるべきです。
今回のサンプルでは、失敗したら再取得をしていますが、その場合失敗クエストのエラー分が無駄になります。
トークンには『有効期限』があるので、例えばセッションに発行した日時を登録してその差で判断するのも良いかもしれません。
またセッション管理ではなく、DB管理にしても良いかもしれません。

この辺りの設計思想は、上流の醍醐味でもあるので沢山悩みましょう...w

メモ

サンプルでは例外に『use Illuminate\Http\Client\RequestException』を利用しています。
このクラスから取得できる抜粋を以下に記載します。

$e->response->status()
$e->response->body()

ログインしてコメントを残そう!!


きっぷる