原文连接: 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