Laravel API Errors and Exceptions: How to Return Responses
https://viblo.asia/p/laravel-api-errors-and-exceptions-how-to-return-responses-924lJLE0KPM
Last updated
https://viblo.asia/p/laravel-api-errors-and-exceptions-how-to-return-responses-924lJLE0KPM
Last updated
Bài đăng này đã không được cập nhật trong 3 năm
Các dự án dựa trên API ngày càng phổ biến và chúng khá dễ tạo trong Laravel. Nhưng một chủ đề ít được nói đến đó là cách xử lý lỗi (errors) đối với các trường hợp ngoại lệ (exception) khác nhau. Người dùng API thường phàn nàn rằng họ nhận được "Server error" nhưng không nhận được message có giá trị. Vậy, làm thế nào để xử lý API errors một cách duyên dáng?
Đối với API, lỗi chính xác thậm chí còn quan trọng hơn so với các dự án web chỉ dành cho trình duyệt. Giống như mọi người, chúng ta có thể hiểu lỗi được thông báo từ trình duyệt và sau đó quyết định phải làm gì, nhưng đối với API - chúng thường được sử dụng bởi các phần mềm khác chứ không phải bởi mọi người, do đó, kết quả trả về nên có thể đọc được bởi các máy. Và điều đó có nghĩa là các HTTP status code.
Mỗi request từ API trả về một status code, với các request success, nó thường là 200 hoặc 2xx với XX là số khác.
Nếu bạn trả về error response, nó sẽ không chứa mã 2xx, đây là những lỗi phổ biến nhất:
Status | Code Meaning |
---|---|
Lưu ý rằng nếu chúng ta không chỉ định status code trả về, thì Laravel sẽ tự động làm điều đó cho chúng ta và điều đó có thể không chính xác. Vì vậy, nên chỉ định code bất cứ khi nào có thể.
Thêm vào đó, chúng ta cần quan tâm đến những human-readable messages. Vì vậy, response tốt điển hình nên chứa HTTP error code và JSON kết quả trả về với nội dung như sau:
Lý tưởng nhất, nó nên chi tiết hơn nữa để giúp người sử dùng API xử lý lỗi. Dưới đây, một ví dụ về cách API Facebook trả về lỗi:
Thông thường, nội dung "error" là nội dung được hiển thị trở lại trình duyệt hoặc ứng dụng di động. Vì vậy, những gì sẽ được đọc bởi con người, chúng ta cần làm cho nó trở nên rõ ràng và với càng nhiều chi tiết cần thiết càng tốt.
Bây giờ, hãy để đến với các tips thực sự về cách làm cho các API error tốt hơn.
Có một setting quan trọng trong file .env
của Laravel - đó là APP_DEBUG
có thể true
hoặc false
.
Nếu bạn set nó thành true
, thì tất cả các errors của bạn sẽ được hiển thị một cách chi tiết, bao gồm tên của các class, DB tables, ...
Đây là một vấn đề lớn về bảo mật, vì vậy trong môi trường production, bạn nên set biến này thành false
.
Nhưng tôi khuyên bạn nên tắt nó cho các dự án API ngay cả trên môi trường local, đây là lý do tại sao:
Bằng cách tắt các errors thực tế, bạn sẽ bị buộc phải suy nghĩ như người sử dụng API, người sẽ chỉ nhận được "Server errors" và không có thêm thông tin. Nói cách khác, bạn sẽ buộc phải suy nghĩ cách xử lý lỗi và cung cấp các thông báo hữu ích từ API.
Tình huống đầu tiên - điều gì sẽ xảy ra nếu ai đó gọi API route không tồn tại, nó thực sự có thể khả thi nếu ai đó thậm chí đã mắc lỗi đánh máy trong URL. Theo mặc định, bạn nhận được phản hồi này từ API:
Và đó là thông báo OK-ish, ít nhất mã 404 được truyền chính xác. Nhưng bạn có thể làm một công việc tốt hơn và giải thích error với một số messages.
Để làm điều đó, bạn có thể chỉ định phương thức Route::fallback()
ở cuối file routes/api.php
, xử lý tất cả các routes không chính xác.
Kết quả sẽ tương tự với 404 response, nhưng bây giờ với error message cung cấp thêm một số thông tin về những việc cần làm với lỗi này.
Một trong những trường hợp Exception thường gặp nhất là "model object is not found", thường được thrown bởi Model::findOrFail($id)
. Nếu chúng ta để nó ở đó, thì message điển hình mà API của bạn sẽ hiển thị:
Nó đúng, nhưng không phải là một message tốt để hiển thị cho end user, phải không? Do đó, lời khuyên của tôi là ghi đè xử lý cho Exception cụ thể đó.
Chúng ta có thể làm điều đó trong app/Exceptions/Handler.php
(hãy nhớ file đó, chúng ta sẽ quay lại nó nhiều lần sau), trong phương thức render()
:
Chúng ta có thể catch
bất kỳ số lượng Exception
nào trong method này. Trong trường hợp này, chúng ta đã trả lại cùng một 404 code nhưng với một message dễ đọc hơn như thế này:
Lưu ý: bạn có biết một phương thức thú vị$exception->getModel()
không? Có rất nhiều thông tin rất hữu ích mà chúng ta có thể nhận được từ object $exception
, ở đây, một ảnh chụp màn hình từ PhpStorm auto-complete:
Trong các project điển hình, các developer không phá vỡ các validate rules, chủ yếu gắn bó với các quy tắc đơn giản như, required
, date
, email
, ... . Nhưng đối với các API, nó thực sự là nguyên nhân gây ra lỗi điển hình nhất - đó là người sử dụng post dữ liệu không hợp lệ và sau đó bị hỏng.
Nếu chúng tôi không đặt thêm effort vào việc catching bad data, thì API sẽ vượt qua xác thực phía back-end và chỉ throw lỗi đơn giản "Server error" mà không có bất kỳ chi tiết nào.
Chúng ta hãy xem ví dụ này - chúng ta có một phương thức store()
trong Controller:
File FormRequest của chúng ta app/Http/Requests/StoreOffericesRequest.php
chứa hai rules:
Nếu chúng ta bỏ lỡ cả hai tham số đó và chuyển các giá trị trống ở đó, API sẽ trả về một lỗi khá dễ đọc với status code là 422 (mã này được tạo theo mặc định của Laravel validation failure):
Như bạn có thể thấy, nó liệt kê tất cả các fields error, cũng đề cập đến tất cả các errors cho từng field, không chỉ lỗi đầu tiên được bắt.
Bây giờ, nếu chúng ta không chỉ định các validate rules đó và cho phép vượt qua validate, thì đây là API trả về:
Là nó. "Server error". Không có thông tin hữu ích khác về những gì đã sai, field nào bị thiếu hoặc không chính xác. Vì vậy, người sử dụng API sẽ không biết phải làm gì.
Vì vậy, tôi sẽ lặp lại quan điểm của mình ở đây - làm ơn, hãy cố gắng nắm bắt càng nhiều tình huống càng tốt trong các validate rules. Kiểm tra sự tồn tại của field, loại của nó, giá trị min-max, duplicate, ...
Tiếp tục với ví dụ trên, các empty error là điều tồi tệ nhất khi sử dụng API. Nhưng thực tế khắc nghiệt là bất cứ điều gì có thể sai, đặc biệt là trong các dự án lớn, vì vậy chúng ta không thể khắc phục hoặc dự đoán các lỗi ngẫu nhiên.
Mặt khác, chúng ta có thể catch
chúng! Với Try catch PHP block.
Hãy tưởng tượng Controller code này:
Nó là một ví dụ hư cấu, nhưng khá thực tế. Tìm kiếm một user bằng email, sau đó tạo record, sau đó thực hiện một cái gì đó với record đó. Và trên bất kỳ step nào, một cái gì đó sai có thể xảy ra. Email có thể trống, không thể tìm thấy admin (hoặc tìm thấy admin sai), phương thức service có thể gây ra bất kỳ lỗi hoặc exception nào khác, ...
Có nhiều cách để xử lý và sử dụng try-catch, nhưng một trong những cách phổ biến nhất là chỉ có một try-catch lớn, với các trường hợp ngoại lệ khác nhau:
Như bạn có thể thấy, chúng ta có thể gọi abort()
bất cứ lúc nào và thêm một error message mà chúng ta muốn. Nếu chúng ta làm điều đó trong mọi Controller (hoặc phần lớn trong số chúng), thì API của chúng ta sẽ trả về 500 tương tự như "Server error", nhưng với nhiều hơn actionable error messages.
Ngày nay, dự án web sử dụng rất nhiều API bên ngoài và chúng cũng có thể fail. Nếu API của họ tốt, thì họ sẽ cung cấp một cơ chế exception và error thích hợp (trớ trêu thay, đó là một điểm của toàn bộ bài viết này), vì vậy hãy để sử dụng nó trong các app của chúng ta.
Ví dụ, hãy để Guzzle curl request đến một số URL và catch exception.
Code đơn giản:
Như bạn có thể thấy, Github URL không hợp lệ và repository này không tồn tại. Và nếu chúng ta để lại code như vậy, API của chúng ta sẽ throw .. đoán xem .. Yup, “500 Server error” không có chi tiết nào khác. Nhưng chúng ta có thể bắt exception và cung cấp thêm chi tiết cho người sử dụng:
Chúng ta thậm chí có thể tiến thêm một bước và tạo exception của riêng mình, liên quan cụ thể đến một số lỗi API của bên thứ 3.
Sau đó, file mới được tạo của chúng ta app/Exceptions/GithubAPIException.php
sẽ giống như sau:
Chúng ta thậm chí có thể để trống, nhưng vẫn throw nó như một exception. Ngay cả tên exception cũng có thể giúp người dùng API tránh các lỗi trong tương lai. Vì vậy, chúng ta làm điều này:
Không chỉ vậy - chúng ta có thể chuyển việc xử lý lỗi đó thành file app/Exceptions/Handler.php
. Như thế này:
Tài liệu: https://laraveldaily.com/laravel-api-errors-and-exceptions-how-to-return-responses/
404
Not Found (page or other resource doesn’t exist)
401
Not authorized (not logged in)
403
Logged in but access to requested area is forbidden
400
Bad request (something wrong with URL or parameters)
422
Unprocessable Entity (validation failed)
500
General server error