web-dev-qa-db-ja.com

REST APIエラーレスポンスモデルとエラーコードシステムを作成する最良の方法は何ですか?

私のREST実装は、次の構造を持つJSONでエラーを返します:

_{
 "http_response":400,
 "dev_message":"There is a problem",
 "message_for_user":"Bad request",
 "some_internal_error_code":12345
}
_

プロパティ(dev_message、message_for_user、some_internal_error_code)に必要な値を渡して返すことができる特別な応答モデルを作成することをお勧めします。コードでは次のようになります。

_$responseModel = new MyResponseModel(400,"Something is bad", etc...);
_

このモデルはどのように見えるべきですか?メソッドを実装する必要がありますか? successResponse()ここで、テキスト情報のみを渡し、コードはデフォルトで200になりますか?私はこれにこだわっています。そしてこれは私の質問の最初の部分です:このモデルを実装する必要がありますか、これは良い練習ですか?今のところ、私はコードから直接配列を返すだけです。

2番目の部分は、エラーコードシステムについてです。エラーコードはドキュメントで説明されます。しかし、私が直面している問題はコードにあります。エラーコードを管理する最良の方法は何ですか?モデル内に記述すべきですか?または、これを処理するための個別のサービスを作成する方が良いでしょうか?

更新1

応答用のモデルクラスを実装しました。それはグレッグの答えに似ており、同じロジックですが、追加的に私は持っています ハードコード モデルに書かれたエラーとここにそれはどのように見えるかです:

_    class ErrorResponse
    {
     const SOME_ENTITY_NOT_FOUND = 100;
     protected $errorMessages = [100 => ["error_message" => "That entity doesn't exist!"]];

     ...some code...
    }
_

なぜこれをしたのですか?そして何のために?

  1. コードではクールに見えます:return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. エラーメッセージを簡単に変更できます。すべてのメッセージは、controller/service/etcなどの代わりに1か所にあります。

これを改善するための提案がある場合は、コメントしてください。

16
Grokking

この状況では、私は常に最初にインターフェースを考え、次にそれをサポートするためにPHPコードを記述します。

  1. REST APIであるため、意味のあるHTTPステータスコードは必須です。
  2. クライアントとの間で送受信される一貫性のある柔軟なデータ構造が必要です。

問題が発生する可能性のあるすべてのものとそれらのHTTPステータスコードについて考えてみましょう。

  • サーバーがエラーをスローする(500)
  • 認証失敗(401)
  • リクエストされたリソースが見つかりませんでした(404)
  • 変更中のデータは、ロードしてから変更されています(409)
  • データ保存時の検証エラー(422)
  • クライアントがリクエストレートを超えました(429)
  • サポートされていないファイル形式(415)

注:後で調査できるものもあります。

ほとんどのエラー状態では、返されるエラーメッセージは1つだけです。 「検証エラー」に使用した422 Unprocessable Entity応答は、複数のエラーを返す可能性があります---フォームフィールドごとに1つ以上のエラー。

エラー応答には柔軟なデータ構造が必要です。

例として、500 Internal Server Error

HTTP/1.1 500 Internal Server Error
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "general": [
            "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
        ]
    }
}

これに対して、サーバーに対してPOST何かを試みたときの単純な検証エラーとは対照的です。

HTTP/1.1 422 Unprocessable Entity
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "first_name": [
            "is required"
        ],
        "telephone": [
            "should not exceed 12 characters",
            "is not in the correct format"
        ]
    }
}

ここで重要なのは、コンテンツタイプがtext/jsonであることです。これは、JSONデコーダーで応答本文をデコードできることをクライアントアプリケーションに通知します。たとえば、内部サーバーエラーが捕捉されず、代わりに一般的な「Something failed bad」ページが配信された場合、コンテンツタイプはtext/html; charset=utf-8である必要があります。これにより、クライアントアプリケーションは応答本文をJSONとしてデコードしようとしません。 。

[〜#〜] jsonp [〜#〜] 応答をサポートする必要があるまで、これはすべてfindとdandyに見えます。失敗した場合でも、200 OK応答を返す必要があります。この場合、クライアントがJSONP応答を要求していることを検出し(通常はcallbackというURL要求パラメーターを検出することにより)、データ構造を少し変更する必要があります。

(GET/posts/123?callback = displayBlogPost)

<script type="text/javascript" src="/posts/123?callback=displayBlogPost"></script>

HTTP/1.1 200 OK
Content-Type: text/javascript
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

displayBlogPost({
    "status": 500,
    "data": {
        "errors": {
            "general": [
                "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
            ]
        }
    }
});

次に、クライアント(Webブラウザー内)の応答ハンドラーには、単一の引数を受け入れるdisplayBlogPostというグローバルJavaScript関数が必要です。この関数は、応答が成功したかどうかを判断する必要があります。

function displayBlogPost(response) {
    if (response.status == 500) {
        alert(response.data.errors.general[0]);
    }
}

だから私たちはクライアントの世話をしました。それでは、サーバーの面倒を見てみましょう。

<?php

class ResponseError
{
    const STATUS_INTERNAL_SERVER_ERROR = 500;
    const STATUS_UNPROCESSABLE_ENTITY = 422;

    private $status;
    private $messages;

    public function ResponseError($status, $message = null)
    {
        $this->status = $status;

        if (isset($message)) {
            $this->messages = array(
                'general' => array($message)
            );
        } else {
            $this->messages = array();
        }
    }

    public function addMessage($key, $message)
    {
        if (!isset($message)) {
            $message = $key;
            $key = 'general';
        }

        if (!isset($this->messages[$key])) {
            $this->messages[$key] = array();
        }

        $this->messages[$key][] = $message;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getStatus()
    {
        return $this->status;
    }
}

そして、サーバーエラーの場合にこれを使用するには:

try {
    // some code that throws an exception
}
catch (Exception $ex) {
    return new ResponseError(ResponseError::STATUS_INTERNAL_SERVER_ERROR, $ex->message);
}

または、ユーザー入力を検証する場合:

// Validate some input from the user, and it is invalid:

$response = new ResponseError(ResponseError::STATUS_UNPROCESSABLE_ENTITY);
$response->addMessage('first_name', 'is required');
$response->addMessage('telephone', 'should not exceed 12 characters');
$response->addMessage('telephone', 'is not in the correct format');

return $response;

その後、返された応答オブジェクトを取得してJSONに変換し、陽気な方法で応答を送信する必要があります。

14
Greg Burghardt