Trong bài học này chúng ta sẽ tiếp tục tìm hiểu về component Validation trong Laraverl. Đây cũng là một component khá phổ biến trong Laravel các bạn nhé!
I. Giới thiệu (Introduction)
Laravel cung cấp cho người dùng một số cách tiếp cận khác nhau để xác thực dữ liệu đến ứng dụng của chúng ta.
Mặc định, lớp base controller App\Http\Controllers\Controller
sử dụng một lớp trait ValidatesRequests
, điều này cung cấp một số method thuận tiện để xác thực các HTTP request với những quy tắc xác thực (validation rule) mạnh mẽ.
II. Bắt đầu nhanh validation (Validation quickstart)
Để học về các tính năng xác thực mạnh mẽ của Laravel, hãy xem xét qua ví dụ đầy đủ bên dưới, bao gồm xác thực form và hiển thị lỗi về người dùng.
1. Định nghĩa route (Defining the route)
Đầu tiên, chúng ta giả sử đăng ký các route sau trong file routes/web.php
.
Route::get('post/create', 'PostController@create');
Route::post('post', 'PostController@store');
Route GET
sẽ hiển thị form để người dụng tạo một bài viết mới, trong khi đó route POST
sẽ nhận request gửi đến từ người dùng và lưu trữ vào database.
2. Khởi tạo controller (Creating the controller)
Bằng lệnh Atisan đơn giản, ta có thể khởi tạo controller PostController
với nội dung như sau:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
public function create()
{
return view('create_post');
}
public function store(Request $request)
{
// Validate...
}
}
Như bạn thấy, mình đã định nghĩa hai method create
và store
tương ứng với hai route ở trên. Tại method store
tạm thời ta sẽ để trống, lát ta sẽ quay lại viết validation logic sau.
3. Khởi tại view (Creating the view)
Theo như ở trên, tiếp theo chúng ta sẽ tạo blade view create_post
, bạn có thể viết nội dung như sau:
resources/views/create_post.blade.php
<h1>Create new post</h1>
<form action=<"/post" method=<"POST">
@csrf
<div>
<p>Title</p>
<input type=<"text" name=<"title">
</div>
<div>
<p>Body</p>
<textarea name=<"body" cols=<"30" rows=<"10"></textarea>
</div>
<br>
<div>
<button type=<"submit">Create</button>
</div>
</form>
Bạn có thể truy cập http://localhost:8000/post/create để xem kết quả.
4. Viết validation logic (Writing the validation logic)
Bây giờ chúng ta đã sẵn sàng viết validation logic trong method store
của controller PostController
rồi. Để làm điều này, chúng ta sẽ sử dụng method validate
được cung cấp bởi Illuminate\Http\Request
object. Nếu vượt qua xác thực, các dòng code tiếp theo của bạn sẽ được thực thi bình thường; còn nếu không, một lỗi ngoại lệ sẽ được đưa ra và lỗi phản hồi phù hợp sẽ được trả về phái người dùng. Trong trường hợp sử dụng HTTP request, một redirect response sẽ được khởi tạo, trong khi một json response sẽ được gửi về nếu sử dụng AJAX request.
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|max:100',
'body' => 'required|min:50',
]);
// The post is valid...
}
Như bạn thấy, mình đã truyền các rule validation vào bên trong method validate
. Bây giờ mình sẽ giải thích đơn giản.
Sau khi request được gửi đến, dữ liệu sẽ được truyền vào method validate
, nó sẽ lọc các request có tên trùng với tên các rule validation đã liệt kê trước đó. Trong trường hợp này là request title
và body
. Nếu tồn tại nó sẽ tiến hành xác thực, còn không thì sẽ bỏ qua.
Đối với request title
mình sẽ xác thực hai rule đó là required
và max:100
, mỗi rule sẽ ngăn cách nhau bởi ký tự |
. Với rule required
, bắt buộc trường title
phải có trong request và không được bỏ trống. Còn với rule max:100
, sẽ giới hạn độ dài của trường title
. Các rule này sẽ được xác thực từ trái sang phải.
Tương tự với trường body
, ta cũng kiểm tra rule required
, nhưng với rule min:50
sẽ bắt buộc độ dài của trường body
phải lớn hơn hoặc bằng 50.
a. Dừng lại khi xác thực đầu tiên thất bại (Stopping on first validation failure)
Thỉnh thoảng bạn muốn dừng lại việc xác thực rule nếu như thất bại ở rule đầu tiên trong một trường nào đó. Bạn có thể khai báo rule bail
như thế này:
$validatedData = $request->validate([
'title' => 'bail|required|max:100',
'body' => 'required|min:50',
]);
Như vậy ta có thể hiểu rằng khi trường title
không tồn tại hoặc trống trong request gửi đến, thì dừng ngay việc validate trường này và chuyển sang trường body
kế tiếp, tức là rule max:100
sẽ không được xác thực. Việc này sẽ giúp ta giảm thiểu thời gian xác thực hơn.
b. Lưu ý về các thuộc tính lồng nhau (A note on nested attributes)
Thoát ra ví dụ một chút, giả sử HTTP request của bạn có chứa các tham số lồng nhau, bạn có thể chỉ định chúng trong rule validation với ký tự .
. Chẳng hạn:
$request->validate([
'author.name' => 'required',
'author.description' => 'required',
]);
5. Hiển thị lỗi xác thực (Displaying the validation error)
Vậy, điều gì sẽ xảy ra nếu một trong các trường request không vượt qua rule validation? Như đã đề cập ở trước, Laravel sẽ tự động chuyển hướng người dùng về vị trí trước, tất cả các lỗi xác thực (validation error) sẽ được flash vào session.
Một lần nữa hãy nhớ, bạn không cần phải truyền bất kỳ dữ liệu nào vào view để có thể hiển thị các validation error. Bởi vì trước khi render view, Laravel sẽ kiểm tra xem có tồn tại validation error nào trong session không. Nếu có, một biến $errors
sẽ được khởi tạo từ lớp Illuminate\Support\MessageBag
cho phép ta tham chiếu tới các validation error.
Biến$errors
được gửi đến view thông qua middlewareIlluminate\View\Middleware\ShareErrorsFromSession
, nó được cung cấp bởi nhóm middlewareweb
. Điều này cho phép bạn có thể thoải mái giả định rằng$errors
luôn xác định và có thể an toàn sử dụng.
Vậy, trong ví dụ của chúng ta, người dùng sẽ được chuyển hướng đến controller action create
khi thất bại trong validation, từ đó có thể hiển thị được validation error. Bạn chỉ cần thêm đoạn code này ở phía trên đầu của nội dung blade view create_post
:
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
<h1>Create new post</h1>
// ...
Quan sát đoạn code trên, mình đã sử dụng method any
từ object $errors
để kiểm tra xem có tồn tại validation error nào không. Nếu có tồn tại thì mình sẽ dùng vòng lặp để in tất cả các lỗi có được thông qua method all
.
Bây giờ các bạn lưu lại, chạy server và thử click nút "Create" mà không nhập bất kỳ dữ liệu nào xem. Kết quả của chúng ta sẽ là:
Mặc định Laravel sẽ tự động tạo thông báo lỗi cho mỗi validation rule. Chắc bạn đang suy nghĩ là làm sao mà thay đổi mấy câu thông báo này theo ý mình được đúng không? Cứ yên tâm, mình sẽ đề cập cách làm ở phần bên dưới. Bây giờ hãy quan sát kết quả, Laravel trả về hai thông báo lỗi đó là title
và body
đang để trống, lý do chúng ta chưa nhập gì cả. Tiếp theo hãy thử nhập dữ liệu như hình dưới:
Giờ hãy thử đoán xem ta có còn bị lỗi nữa không? Câu trả lời là "Có", nếu không tin bạn có thể nhấn "Create" để kiểm chứng.
Đến đây chắc các bạn cũng hình dung ra lý do vì sao rồi đúng không? Chính là do rule min:50
của trường body
, nên với chuỗi My body
quá ít ký tự không thể vượt qua validation.
Ngoài ra, bạn cũng có tách các thông báo lỗi này riêng cho từng input với thẻ @error
trong blade template. Hãy nhìn vào ví dụ bên dưới:
<style>
.error {
color: red;
}
</style>
<h1>Create new post</h1>
<form action=<"/post" method=<"POST">
@csrf
<div>
<p @error('title') class=<"error" @enderror>
Title
@error('title')
: <span>{{ $message }}</span>
@enderror
</p>
<input type=<"text" name=<"title">
</div>
<div>
<p @error('body') class=<"error" @enderror>
Body
@error('body')
: <span>{{ $message }}</span>
@enderror
</p>
<textarea name=<"body" cols=<"30" rows=<"10"></textarea>
</div>
<br>
<div>
<button type=<"submit">Create</button>
</div>
</form>
Mình sử dụng cặp thẻ @error
để bắt thông báo lỗi cho vị trí nào mà mình muốn, nếu tồn tại validation error thì mọi đoạn code giữa cặp thẻ sẽ được thực thi. Ngoài ra câu thông báo lỗi đầu tiên sẽ được lưu trong $message
, được khai báo trong cặp thẻ @error
.
6. Lưu ý về các trường tùy chọn (A note on optional fields)
Mặc định, Laralve liên kết với hai middleware TrimStrings
và ConvertEmptyStringsToNull
. Vì thế, nếu ứng dụng có những trường tùy chọn, tức là có thể trống cũng được, còn nếu không trống thì phải validate chẳng hạn, thì bạn có thể sử dụng rule nullable
để thiết lập tùy chọn này.
$request->validate([
'birthday' => 'nullable|date',
]);
Với trường birthday
, nếu chúng ta nhập dữ liệu thì nó sẽ đi qua rule date
để kiểm tra xem có phải định dạng ngày tháng hay không; còn nếu bạn không nhập gì thì framework sẽ bỏ qua kiểm duyệt trường birthday
này.
7. AJAX request & validation
Trong ví dụ trên, chúng ta đã sử dụng form truyền thống để gửi dữ liệu đến ứng dụng. Tuy nhiên, một vài ứng dụng cần gửi dữ liệu qua AJAX request. thì sao. Như đã nói ở trên, khi sử dụng method validate
trong AJAX request, Laravel sẽ không tạo redirect response mà sẽ trả về json response chứa các validation error kèm theo đó là mã status HTTP 422.
Các bạn có thể thay đổi nội dung blade view create_post
như sau:
<h1>Create new post</h1>
<form action=<"/post" method=<"POST">
@csrf
<div>
<p>Title</p>
<input type=<"text" name=<"title">
</div>
<div>
<p>Body</p>
<textarea name=<"body" cols=<"30" rows=<"10"></textarea>
</div>
<br>
<div>
<button type=<"submit">Create</button>
</div>
</form>
<script src=<"https://code.jquery.com/jquery.min.js"></script>
<script>
$('form').submit(function(e) {
e.preventDefault();
$.ajax({
url: '/post',
type: 'POST',
data: {
_token: $('input[name=_token]').val(),
title: $('input[name=title]').val(),
body: $('textarea[name=body]').val()
}, success: function(res) {
//
}, error: function(error) {
console.log(error);
}
})
})
</script>
Như các bạn thấy, mình đã thay đổi các form truyền thống sang gửi bằng AJAX request. Bây giờ hãy thử click "Create" với hai trường title
và body
để trống, bật console lên để xem kết quả.
Laravel đã trả về một object error cùng với status code 422, việc của ta bây giờ chỉ tham chiếu object đó là lấy ra các thông báo lỗi cần thiết.