原文連接: https://learnku.com/laravel/t...
討論請前往專業的 Laravel 開發者論壇: https://learnku.com/Laravel
基於 API 的項目開發愈來愈受歡迎,而且使用 Laravel 就能很容易實現。可是在針對如何處理各類異常的話題不多被說起。因此 API 的使用者們常常會抱怨除了收到 Server error ,不多有更多的錯誤信息。那麼,咱們該如何優雅的處理 API 錯誤讓其變得更具備可讀性呢?php
對於 API 開發來說,正確的錯誤描述甚至比僅基於 Web 瀏覽器的項目更爲重要。做爲使用者,咱們也能夠經過瀏覽器消息提示清楚地瞭解錯誤以及該怎麼解決。但對於 API 自己來講,它們是由軟件而非人員使用的,所以返回的結果應 readable by machines 。這意味着HTTP狀態代碼就必不可少。laravel
API 給每一個請求都會返回一個狀態碼,請求成功一般是 200,或者是以 2 開頭的其餘狀態碼。git
若是返回錯誤響應,則該響應不該包含2xx代碼,如下是最多見的錯誤代碼:github
| 狀態碼 | 描述 |
| 404 | 未找到(請求資源不存在) |
| 401 | 未認證 (須要登陸) |
| 403 | 沒有權限 |
| 400 | 錯誤的請求(URL或參數不正確) |
| 422 | 驗證失敗 |
| 500 | 服務器錯誤 |web
注意:返回響應時,若是沒有添加狀態碼,Laravel 會自動指定狀態碼,但並不能保證所指定的狀態碼正確。因此最好仍是本身手動添加正確的狀態碼。數據庫
除此以外,咱們還要考慮到 human-readable messages。所以,典型的響應應包含 HTTP 錯誤代碼和 JSON 結果,以下所示: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
如今,讓咱們瞭解如何更好地改善 API 的錯誤提示。瀏覽器
Laravel 的 .env 文件有一個重要的設置 APP_DEBUG ,它的值能夠爲 false or true。
若是設置爲 true, 則將顯示全部錯誤以及詳細信息,包括類名稱,數據庫表等。
這是一個巨大的安全問題,所以在生產環境中,強烈建議將其設置爲 false。
可是,我建議即便在本地也要針對 API 項目將其關閉,緣由以下。
關閉實際錯誤後,您將被迫像 API 使用者那樣思考,由於他們只會收到服務器錯誤(返回 Server error)而沒有更多的信息。換句話說,這時候你就須要考慮如何處理錯誤並提供合適的響應消息。
第一種狀況-若是有人調用不存在的 API 怎麼辦,有人甚至在 URL 中輸入錯誤的地址。默認狀況下,您從 API 得到如下響應:
Request URL: http://q1.test/api/v1/offices Request Method: GET Status Code: 404 Not Found { "message": "" }
至少 404 響應成功。其實能夠作得更好,能夠經過一些消息來解釋錯誤。
爲此你能夠在 routes/api.php 的末尾指定 Route::fallback() 方法, 處理全部訪問不存在路由的請求。
Route::fallback(function(){ return response()->json([ 'message' => 'Page Not Found. If error persists, contact info@website.com'], 404); });
結果仍是相同的404響應,但如今出現了錯誤消息,提供了有關如何處理此錯誤的更多信息。
最多見就是找不到某些模型對象,一般由 Model :: findOrFail($ id) 拋出。如下是你的 API 會顯示的典型消息:
{ "message": "No query results for model [App\\Office] 2", "exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", ... }
這是正確的,但向最終用戶顯示的消息不是很漂亮,所以,個人建議是重寫對該特定異常的處理。
咱們能夠在 app/Exceptions/Handler.php (請記住該文件,咱們將在之後屢次返回它)中使用 render() 方法:
// 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()
?咱們能夠從 $Exception 對象中得到不少很是有用的信息,下面是 PhpStorm 自動完成的屏幕截圖::
開發人員通常不會考慮過多的驗證規則,而是堅持使用諸如 required,date,emai 之類的簡單規則。可是對於 API 而言,實際上錯誤的最典型緣由是-消費者提交無效數據。
若是咱們不花更多的精力來收集未經過驗證的數據,那麼 API 將經過後端驗證,並拋出簡單的 Server error,而沒有任何詳細信息(實際上緣由是數據庫查詢錯誤)。
讓咱們看一下這個示例–咱們在 Controller 中有一個 store() 方法:
public function store(StoreOfficesRequest $request) { $office = Office::create($request->all()); return (new OfficeResource($office)) ->response() ->setStatusCode(201); }
咱們的 FormRequest 文件 app/Http/Requests/StoreOfficesRequest.php 包含兩個規則:
public function rules() { return [ 'city_id' => 'required|integer|exists:cities,id', 'address' => 'required' ]; }
若是咱們遺漏了這兩個參數並在其中傳遞空值,API 將返回一個至關易讀的錯誤,帶有 422 狀態碼(此狀態碼默認是因爲 Laravel 驗證失敗而產生):
{ "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."] } }
它列出了全部字段錯誤,還提到了每一個字段的全部錯誤,而不只僅是捕獲到的第一個錯誤。
如今,若是咱們不指定那些驗證規則並容許驗證經過,如下是 API 返回:
{ "message": "Server Error" }
僅僅是服務器錯誤,沒有其餘有用的信息,什麼是錯誤的,什麼字段是缺失或不正確的。所以 API 使用者會懵逼。
因此我將在這裏重複個人觀點-請嘗試在驗證規則中捕獲儘量多的可能狀況。檢查字段是否存在、類型、最小-最大值、重複等
繼續上面的示例,使用 API 時,最糟糕的事情就是空錯誤。可是任何事情都會出錯,尤爲是在大型項目中,咱們沒法修復或預測隨機錯誤。
可是,咱們能夠捕獲他們!使用 try-catch PHP block。
想象一下這個控制器代碼:
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); }
這是一個虛構的例子,也很常見。用電子郵件搜索用戶,而後建立一條記錄,對該記錄進行操做。而且在任何步驟上,均可能發生錯誤。電子郵件可能爲空,可能找不到管理員(或發現錯誤的管理員),服務方法可能會引起任何其餘錯誤或異常等。
有不少處理和使用 try-catch 的方法,可是最流行的方法之一就是隻捕獲一個大的try-catch,而後對應是哪一個異常類拋出的:
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'); }
這樣,咱們能夠隨時調用 abort() 並添加所需的錯誤消息。若是咱們在每一個控制器(或其中的大多數控制器)中執行此操做,那麼咱們的 API 將返回與 Server error 相同的500,但包含更多可操做的錯誤消息。
現在,Web 項目使用大量外部 API,它們也可能會失敗。若是他們的 API 不錯,那麼他們將提供適當的異常和錯誤機制,所以咱們須要在應用程序中使用它。
例如,對某些 URL進行 Guzzle curl 請求並捕獲異常。
代碼很簡單:
$client = new \GuzzleHttp\Client(); $response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456'); // ... 用該響應作點什麼
您可能已經注意到,Github URL 無效,而且該存儲庫不存在。並且,若是咱們將代碼保持原樣,咱們的 API 將拋出 500 Server error,沒有其餘詳細信息。可是咱們能夠捕獲異常,並向消費者提供更多詳細信息:
// 在頂部 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'); }
咱們甚至能夠更進一步,建立咱們本身的異常,特別是與一些第三方 API 錯誤相關的異常。
php artisan make:exception GithubAPIException
而後,咱們新生成的文件 app/Exceptions/GithubAPIException.php將以下所示:
namespace App\Exceptions; use Exception; class GithubAPIException extends Exception { public function render() { // ... } }
咱們甚至可讓它爲空,但仍是把它看成異常拋出。即便是異常 name,也能夠幫助 API 用戶避免未來的錯誤。因此咱們這樣作:
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'); }
不只如此-咱們能夠將錯誤處理移至 app / Exceptions / Handler.php 文件中(還記得上面嗎?),以下所示:
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); }
以上就是我處理 API 錯誤的技巧,但這不是嚴格的規則。每一個人均可以有本身的想法,若是你有本身的一些見解,能夠在下面發表評論並進行討論。
最後,除了錯誤處理以外,我想鼓勵你作兩件事:
原文連接: https://learnku.com/laravel/t...
討論請前往專業的 Laravel 開發者論壇: https://learnku.com/Laravel