LaravelのAPIのエラーと例外:応答を返す方法

LaravelのAPIのエラーと例外:応答を返す方法

APIベースのプロジェクトはますます人気があり、それらはLaravelに作成するのは簡単です。しかし、一つのトピックはあまり語られている - それはさまざまな例外のエラー処理です。APIの消費者は、多くの場合、彼らは、「サーバーエラー」が、無価値のあるメッセージを取得することを訴えます。だから、どのように優雅にAPIのエラーを処理するには?「読める」形式でそれらを返却するには?

主な目標:ステータスコード+読み取り可能メッセージ

APIのために、正しいエラーでも、ウェブブラウザだけのプロジェクトのためのよりも重要です。人として、私たちは、ブラウザのメッセージからエラーを理解し、次に何をすべきかを決定、しかし、APIのことができます - 彼らは通常、他のソフトウェアではなく、人々によって消費されるので、返される結果は「機械によって読み取り可能」でなければなりません。そして、それは、HTTPステータスコードを意味しています。

APIへのすべての要求は、それは通常200、または他の番号などのXXと2xxのだ成功した要求のために、いくつかのステータスコードを返します。

あなたはエラー応答を返した場合、それは2xxのコードを含むべきではない、ここでエラーのための最も人気のあるものは以下のとおりです。

ステータスコード 意味
404 (ページや他のリソースが存在しない)が見つかりませんでした
401 認証されていません(ログインしていません)
403 ログインするだけで、要求された領域へのアクセスは禁止されています
400 不正なリクエスト(URLやパラメータに何らかの問題)
422 処理不能エンティティ(検証が失敗しました)
500 一般的なサーバーエラー

私達はリターンのステータスコードを指定しない場合、Laravelは私たちのために自動的にそれを行うだろうことに注意してください、それは間違っている可能性があります。だから、可能な限りのコードを指定することをお勧めします。

それに加えて、我々はの世話をする必要がある  人間が読めるメッセージだから、典型的な良好な応答は次のようなものとHTTPエラーコードとJSONの結果が含まれている必要があります。

{
    "error": "Resource not found"
}

理想的には、エラーに対処するためのAPIの消費者を助けるために、さらに多くの詳細が含まれている必要があります。ここではFacebookのAPIがエラーを返す方法の例を示します。

{
  "error": {
    "message": "Error validating access token: Session has expired on Wednesday, 14-Feb-18 18:00:00 PST. The current time is Thursday, 15-Feb-18 13:46:35 PST.",
    "type": "OAuthException",
    "code": 190,
    "error_subcode": 463,
    "fbtrace_id": "H2il2t5bn4e"
  }
}

通常は、「エラー」の内容は、バックブラウザやモバイルアプリに示されているものです。それは、人間が読むことになるものですので、したがって、我々は、必要に応じてとして多くの詳細と、明確にすることの世話をする必要があります。

それでは、より良いAPIエラーを作る方法を、実際の先端に取得してみましょう。


ヒント1.スイッチAPP_DEBUG =偽であってもローカルで

1つの重要な設定があります  .env  Laravelのファイルは、 -それはだ  APP_DEBUG  することができ  、偽  または 

あなたにそれをオンにした場合は  、その後、すべてのエラーがなどのクラスの名前、DBのテーブルを含む、すべての詳細が表示されます

本番環境では、厳密にこれを設定することをお勧めしていますので、それは、巨大なセキュリティ上の問題で  はfalse

しかし、私はここに理由です、でもローカルAPIのプロジェクトのためにそれをオフに助言します。

実際のエラーをオフにすることで、あなたがされ  強制的に  だけ受け取ることになるAPIの消費者のように考えるために  、「サーバーエラー」  とこれ以上の情報を。言い換えれば、あなたはエラーを処理し、APIから有用なメッセージを提供する方法を考えることを強制されます。


ヒント2.未処理のルート - フォールバックメソッド

第1の状況 - 誰かが存在しないAPIのルートを呼び出す場合、誰かがさえURLのタイプミスをした場合、それは本当に可能できますか。デフォルトでは、APIからこの応答を取得します:

Request URL: http://q1.test/api/v1/offices
Request Method: GET
Status Code: 404 Not Found
{
    "message": ""
}

そして、それは、少なくとも404のコードが正しく渡され、OKっぽいメッセージです。しかし、あなたはより良い仕事をすると、いくつかのメッセージでエラーを説明することができます。

これを行うには、次のように指定することができます  ルート::フォールバック()  の終わりに方法を  ルート/ api.php一致しなかったすべてのルートを取り扱い、。

Route::fallback(function(){
    return response()->json([
        'message' => 'Page Not Found. If error persists, contact [email protected]'], 404);
});

結果は同じ404応答であるが、今はこのエラーをどうするかについてのいくつかのより多くの情報を与えるエラーメッセージとなります。


ヒント3.オーバーライド404 ModelNotFoundException

ほとんどの場合、例外の一つは、いくつかのモデルオブジェクトは、通常によってスローされ、発見されないということである  モデル:: findOrFail($のID) 我々はそのままにしておく場合は、ここにあなたのAPIが表示されます典型的なメッセージです:

{
    "message": "No query results for model [App\\Office] 2",
    "exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
    ...
}

それは右、エンドユーザーに表示する非常にきれいなメッセージが正しいですが、ありませんか?そこで私のアドバイスは、その特定の例外の処理をオーバーライドすることです。

私たちは、その中に行うことができます  アプリ/例外/ Handler.php  で、(私たちは、後でそれに戻って何度も来る、そのファイルを覚えている)  レンダリング()  メソッド:

// Don't forget this in the beginning of file
use Illuminate\Database\Eloquent\ModelNotFoundException;

// ...

public function render($request, Exception $exception)
{
    if ($exception instanceof ModelNotFoundException) {
        return response()->json([
            'error' => 'Entry for '.str_replace('App\\', '', $exception->getModel()).' not found'], 404);
    }

    return parent::render($request, $exception);
}

私たちは、この方法では、例外の任意の数をキャッチすることができます。このケースでは、我々はこのような、より読みやすいメッセージと同じ404コードを返します:

{
    "error": "Entry for Office not found"
}

ご注意:あなたが面白い方法に気づいた  $ exception-> getModel()は我々は$例外オブジェクトから取得することができる非常に多くの有用な情報があります、ここでPhpStormのオートコンプリートからのスクリーンショットです:


検証では、できるだけ多くのヒント4.キャッチ

その消費者のポストに無効なデータを、そして - 典型的なプロジェクトでは、開発者が「必須」、「日付」、「電子メール」などのような単純なもので、主に固執しかし、APIのそれは実際にエラーの最も一般的な原因ですが、検証ルールをoverthinkませんその後、休憩を詰め込みます。

我々は悪いデータをキャッチして、余分な力を入れない場合は、APIは、バックエンドの検証に合格し、(実際にはDBクエリエラーを意味します)いずれかの詳細がなくて単なる「サーバーエラー」がスローされます。

のは、この例を見てみましょう-私たちが持っている  店舗()  コントローラのメソッドを:

public function store(StoreOfficesRequest $request)
{
    $office = Office::create($request->all());
    
    return (new OfficeResource($office))
        ->response()
        ->setStatusCode(201);
}

Our FormRequest file app/Http/Requests/StoreOfficesRequest.php contains two rules:

public function rules()
{
    return [
        'city_id' => 'required|integer|exists:cities,id',
        'address' => 'required'
    ];
}

If we miss both of those parameters and pass empty values there, API will return a pretty readable error with 422 status code (this code is produced by default by Laravel validation failure):

{
    "message": "The given data was invalid.",
    "errors": { 
        "city_id": ["The city id must be an integer.", "The city id field is required."],
        "address": ["The address field is required."]
    }
}

As you can see, it lists all fields errors, also mentioning all errors for each field, not just the first that was caught.

Now, if we don’t specify those validation rules and allow validation to pass, here’s the API return:

{
    "message": "Server Error"
}

That’s it. Server error. No other useful information about what went wrong, what field is missing or incorrect. So API consumer will get lost and won’t know what to do.

So I will repeat my point here – please, try to catch as many possible situations as possible within validation rules. Check for field existence, its type, min-max values, duplication etc.


Tip 5. Generally Avoid Empty 500 Server Error with Try-Catch

Continuing on the example above, just empty errors are the worst thing when using API. But harsh reality is that anything can go wrong, especially in big projects, so we can’t fix or predict random bugs.

On the other hand, we can catch them! With try-catch PHP block, obviously.

Imagine this Controller code:

public function store(StoreOfficesRequest $request)
{
    $admin = User::find($request->email);
    $office = Office::create($request->all() + ['admin_id' => $admin->id]);
    (new UserService())->assignAdminToOffice($office);
    
    return (new OfficeResource($office))
        ->response()
        ->setStatusCode(201);
}

It’s a fictional example, but pretty realistic. Searching for a user with email, then creating a record, then doing something with that record. And on any step, something wrong may happen. Email may be empty, admin may be not found (or wrong admin found), service method may throw any other error or exception etc.

There are many way to handle it and to use try-catch, but one of the most popular is to just have one big try-catch, with catching various exceptions:

try {
    $admin = User::find($request->email);
    $office = Office::create($request->all() + ['admin_id' => $admin->id]);
    (new UserService())->assignAdminToOffice($office);
} catch (ModelNotFoundException $ex) { // User not found
    abort(422, 'Invalid email: administrator not found');
} catch (Exception $ex) { // Anything that went wrong
    abort(500, 'Could not create office or assign it to administrator');
}

As you can see, we can call abort() at any time, and add an error message we want. If we do that in every controller (or majority of them), then our API will return same 500 as “Server error”, but with much more actionable error messages.


Tip 6. Handle 3rd Party API Errors by Catching Their Exceptions

These days, web-project use a lot of external APIs, and they may also fail. If their API is good, then they will provide a proper exception and error mechanism (ironically, that’s kinda the point of this whole article), so let’s use it in our applications.

As an example, let’s try to make a Guzzle curl request to some URL and catch the exception.

Code is simple:

$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456');
// ... Do something with that response

As you may have noticed, the Github URL is invalid and this repository doesn’t exist. And if we leave the code as it is, our API will throw.. guess what.. Yup, “500 Server error” with no other details. But we can catch the exception and provide more details to the consumer:

// at the top
use GuzzleHttp\Exception\RequestException;

// ...

try {
    $client = new \GuzzleHttp\Client();
    $response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456');
} catch (RequestException $ex) {
    abort(404, 'Github Repository not found');
}

Tip 6.1. Create Your Own Exceptions

We can even go one step further, and create our own exception, related specifically to some 3rd party API errors.

php artisan make:exception GithubAPIException

Then, our newly generated file app/Exceptions/GithubAPIException.php will look like this:

namespace App\Exceptions;

use Exception;

class GithubAPIException extends Exception
{

    public function render()
    {
        // ...
    }

}

We can even leave it empty, but still throw it as exception. Even the exception name may help API user to avoid the errors in the future. So we do this:

try {
    $client = new \GuzzleHttp\Client();
    $response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456');
} catch (RequestException $ex) {
    throw new GithubAPIException('Github API failed in Offices Controller');
}

Not only that – we can move that error handling into app/Exceptions/Handler.php file (remember above?), like this:

public function render($request, Exception $exception)
{
    if ($exception instanceof ModelNotFoundException) {
        return response()->json(['error' => 'Entry for '.str_replace('App\\', '', $exception->getModel()).' not found'], 404);
    } else if ($exception instanceof GithubAPIException) {
        return response()->json(['error' => $exception->getMessage()], 500);
    } else if ($exception instanceof RequestException) {
        return response()->json(['error' => 'External API call failed.'], 500);
    }

    return parent::render($request, $exception);
}

Final Notes

So, here were my tips to handle API errors, but they are not strict rules. People work with errors in quite different ways, so you may find other suggestions or opinions, feel free to comment below and let’s discuss.

Finally, I want to encourage you to do two things, in addition to error handling:

  • Provide detailed API documentation for your users, use packages like API Generator for it;
  • While returning API errors, handle them in the background with some 3rd party service like Bugsnag / Sentry / Rollbar. They are not free, but they save massive amount of time while debugging. Our team uses Bugsnag, here’s a video example.
Like our articles?

おすすめ

転載: www.cnblogs.com/mouseleo/p/11409645.html