💚

LaravelでRFC9457に準拠したエラーレスポンスを実装する方法

2024/12/08に公開

🔎 RFC9457とは

RFC9457は、HTTP APIでエラーが発生した際に、エラーレスポンスを統一的なフォーマットで返すための仕様(標準)を定めた文書です。
これに従うことで、API利用者はどのエラーも同じ形式で受け取れるため、エラーの内容を機械的・人的に理解しやすくなります。

なぜRFC9457に準拠するのか?

  • 一貫性あるエラーフォーマット:APIの開発者や利用者が、どんなエラーでも同じ枠組みで解析できます。
  • 明確なエラー情報:エラーの種類、タイトル、詳細説明などが明確に定義されているため、問題解決が容易になります。

📝 カスタム例外インターフェースの作成

まずは、RFC9457形式でエラーレスポンスを返すために必要なメソッドを定義した、インターフェースを用意します。

<?php
// app/Exceptions/ApiExceptionInterface.php

namespace App\Exceptions;

use Throwable;

interface ApiExceptionInterface extends Throwable
{
    public function getType(): string;       // エラータイプ(URI形式)
    public function getTitle(): string;      // エラータイトル
    public function getStatusCode(): int;    // HTTPステータスコード
    public function getDetail(): string;     // 詳細なエラー説明
}

このインターフェースを実装することで、どのカスタム例外でも同じ項目を揃えられます。

🛠️ カスタム例外クラスの実装

次に、上記インターフェースを実装した「カスタム例外クラス」を作成します。
ここでは ApiException というクラスを例にします。

<?php
// app/Exceptions/ApiException.php

namespace App\Exceptions;

use Exception;

class ApiException extends Exception implements ApiExceptionInterface
{
    public function getType(): string
    {
        return 'https://example.com/api-error'; 
    }

    public function getTitle(): string
    {
        return 'Api Error';
    }

    public function getStatusCode(): int
    {
        return 500;
    }

    public function getDetail(): string
    {
        return 'Unexpected error occurred.';
    }
}

このクラスは、エラーが発生した時に固定的なタイトルや詳細を返しますが、実際にはコンストラクタで動的にメッセージを受け取り、それを getDetail() で返すようにすることで、より柔軟に扱えます。

🔥 例外のスロー

コントローラー内でエラーが起きたら、上記のカスタム例外を投げます。
これにより、後述の例外ハンドラーがRFC9457形式でレスポンスを返します。

<?php
// app/Http/Controllers/ExampleController.php

namespace App\Http\Controllers;

use App\Exceptions\ApiException;
use Illuminate\Http\Request;

class ExampleController extends Controller
{
    public function doSomething(Request $request)
    {
        try {
            // 何らかの処理
            // ...
            // エラー発生時に例外をスロー
            throw new ApiException('予期しないエラーが発生しました');
        } catch (ApiException $e) {
            // ここでは再スローしないで例外ハンドラーに任せる
            throw $e;
        }
    }
}

⚙️ 例外ハンドリングの設定

Laravelには例外を処理するための仕組み(エラーハンドラー)が用意されています。ここに、 ApiExceptionInterface を実装している例外が投げられたら、RFC9457の形式でJSONレスポンスを返すようにします。

<?php
// bootstrap/app.php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use App\Exceptions\ApiExceptionInterface;
use Illuminate\Support\Facades\Response;

return Application::configure(basePath: dirname(__DIR__))

->withExceptions(function (Exceptions $exceptions) {
    // ApiExceptionInterfaceを実装する例外の処理方法を定義
    $exceptions->renderable(function (ApiExceptionInterface $exception, $request) {
        return Response::json([
            'type'    => $exception->getType(),    // エラー種別のURI
            'title'   => $exception->getTitle(),   // エラータイトル
            'status'  => $exception->getStatusCode(), // HTTPステータスコード
            'detail'  => $exception->getDetail(),   // エラー詳細メッセージ
            'instance'=> $request->path(),          // 発生したエンドポイントのパス
        ], $exception->getStatusCode());
    });
});

✍️ まとめ

  • カスタム例外インターフェース:RFC9457形式でエラー情報を提供するための約束事を定義。
  • カスタム例外クラス:インターフェースを実装し、統一的なエラーレスポンスを返す準備。
  • コントローラーでのスロー:実際のビジネスロジックでエラーが起きたら、カスタム例外を投げる。
  • 例外ハンドラーでの処理:カスタム例外をキャッチし、RFC9457に準拠したJSONレスポンスを返す。

こうすることで、APIのエラー応答がわかりやすくなり、API利用者は発生した問題をより簡単に理解・解決できます。

📚 参考資料

Discussion