Validation trong Laravel (P1)

Giới thiệu

Khi làm việc với bất kì ngôn ngữ lập trình nào thì việc kiểm tra tính hợp lệ của các dữ liệu input là điều không thể thiếu, đặc biệt khi dữ liệu input đến từ phía người sử dụng thông qua các dạng form. Bài viết này sẽ giới thiệu cho bạn về giải pháp mà Laravel cung cấp cho bạn để thực hiện công việc kiểm tra dữ liệu đầu vào này.

Vấn đề

Thông thường khi có dữ liệu gửi đến từ phía người dùng ta sẽ phải thực hiện công việc kiểm tra xem dữ liệu đó có thỏa mãn các yêu cầu mà chúng ta đặt ra không trước khi tiến hành xử lý tiếp các công việc khác. Giả sử ta có một form nhập liệu như sau: Khi dữ liệu được gửi lên từ phía client, chúng ta cần kiểm tra xem dữ liệu mà người dùng nhập vào có thỏa mãn một số yêu cầu như sau:

  • Dữ liệu nhập vào không được để trống
  • Dữ liệu nhập vào phải là một số
  • Dữ liệu nhập vào phải lớn hơn 10

Đối với yêu cầu nói trên thì đây là cách kiểm tra dữ liệu có thể nói là "thủ công" khi dùng Laravel:

public function store(Request $request)
{
    $input = $request->input('number');

    if ($input == '') {

        return 'Number is required';

    } elseif (!is_numeric($input)) {

        return 'This is not a number, please try again';

    } elseif ((int) $input < 10) {

        return 'Number must greater than 10';

    } else {

        return 'Correct';
    }
}

Như bạn có thể thấy đối với cách làm trên, ta đã có thể kiểm tra được dữ liệu đối với yêu cầu đặt ra. Tuy nhiên đối với cách làm trên, bạn có thể thấy chỉ với duy nhất một field input mà ta cần tốn rất nhiều dòng code để có thể kiểm tra nó. Thử tưởng tượng nếu form của chúng ta có nhiều field hơn thì method của chúng ta sẽ ngày càng trở nên cồng kềnh dẫn đến controller cũng trở nên cồng kềnh theo. Để giải quyết cũng như đơn giản hóa vấn đề này, Laravel cung cấp cho chúng ta một phương pháp khác để có thể kiểm tra dữ liệu một cách ngắn gọn và đơn giản hơn rất nhiều thông qua ValidatesRequests trait nằm ở class App\Http\Controllers\Controller.

Validation trong Laravel

1. Simple request validate

Để hiểu được cách xử dụng của ValidatesRequests trait trong Laravel ta sẽ sử dụng ví dụ với form nhập liệu như sau:

Với các yêu cầu về dữ liệu nhập vào được xét như sau:

  • Tất cả các field không được để trống
  • username chỉ gồm các chữ cái và n kí tự thỏa mãn: 4 <= n <= 10
  • email đúng định dạng email
  • password gồm tối thiểu 8 kí tự
  • passwrod confirm phải giống với password

Nếu bạn tham khảo document cho phần validation trong Laravel ta được cung cấp một ví dụ như sau:

public function store(Request $request)
{
    $validatedData = $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);
}

Theo như document là Laravel cung cấp, ở đây, ta đang sử dụng chức năng validate() được cung cấp bở class Request, nếu các field của chúng ta thỏa mãn các điều kiện theo yêu cầu thì hàm store sẽ tiếp tục xử lý bình thường. Nếu mộ trong các field không thỏa mã yêu cầu sẽ sinh ra Exception đồng thời trả lại lỗi cho người dùng ở dạng phù hợp (có thể dạng HTTP Response hoặc JSON nếu request trước đó là ajax). Nội dung đoạn code trên được hiểu đơn giản như sau:

  • Object Request sẽ thực hiện kiểm tra các field từ form mà người dùng nhập với tên field là key của mảng, VD: với keytitle như hình ta thấy ở trên thì thực chất bên HTML sẽ là thẻ input có name tương ứng là titlenhư sau:
    <input type="text" name="title">
    
  • Tương tự với các key còn lại trong mảng mà ta truyền vào hàm validate() cũng là name của các field mà ta muốn tiến hành kiểm trả dữ liệu
  • Tiếp đến phần value ứng với mỗi key đó chính là những nội dung mà chúng ta cần kiểm tra với field. Cụ thể với field title, ta có điều kiện:
    'title' => 'required|unique:posts|max:255',
    
  • Điều này có nghĩa là với field title ta sẽ kiểm tra 3 điều kiện với mỗi điều kiện sẽ cách nhau bởi dấu |, các điều kiện lần lượt là:
    • required - title phải tồn tại trong request gửi lên đồng thời không được để trống
    • unique:posts - title nội dung của nó phải là duy nhất đối với bảng posts
    • max:255 - Độ dài tối đa của title tính theo số kí tự là 255 kí tự
  • Đây là các điều kiện có sẵn mà chức năng Validation trong Laravel cung cấp sẵn cho chúng ta, để có thể tìm hiểu kĩ hơn về các điền kiện kiểm tra này, bạn có thể tham khảo tại đây.

Để thử nghiệm chức năng trên đối với form của chúng ta, ta sẽ sửa lại đôi chút hàm ban đầu như sau:

public function store(Request $request)
{
    $validatedData = $request->validate([
        'username' => 'required',
        'email' => 'required',
        'password' => 'required',
        'password_confirmation' => 'required',
    ]);
}

Với username, email, password, password_confirmationname của các input field mà ta đặt bên html. Với đoạn code xử lý kiểm tra dữ liệu như trên, khi ta submit form mà không nhập bất cứ nội dung gì cả, thì đây sẽ là những gì ta thu được:

Lưu ý : ta nhận được khung đỏ báo lỗi không phải do Laravel tự sinh cho chúng ta mà do chúng ta tạo. Cụ thể sẽ nói rõ trong phần sau. Như bạn có thể thấy, khi chúng ta không nhập dữ liệu gì, đoạn code kiểm tra dữ liệu của chúng ta sẽ thực hiện kiểm trả cả 4 field được nêu ra trong mảng cùng với điều kiện của 4 field đó là required. Do tất cả các field ta đều để rỗng dẫn đến việc kiểm tra dữ liệu trả về là không chính xác và tự động quay lại (redirect) về form của chúng ta với nội dung báo lỗi như ở khung đỏ trong hình ở trên. Đoạn code trên ta đã đảm bảo được điều kiện thứ nhất trong danh sách các yêu cầu mà chúng ta cần thực hiện là tất cả các field không được để trống. Tiếp theo ta sẽ lần lượt thêm các điều kiện cho mỗi field để hoàn thiện các điều kiện đã đặt ra như sau:

public function store(Request $request)
{
    $validatedData = $request->validate([
        'username' => 'required|alpha|min:6|max:10',
        'email' => 'required|email',
        'password' => 'required|min:8',
        'password_confirmation' => 'required|same:password',
    ]);
}

Sau khi thử nhập lại dữ liệu nhưng vẫn vi phạm điều kiện như sau:

Đây là kết quả mà chúng ta thu được:

Như bạn thấy, do các field của chúng ta đã vi phạm các điều kiện mới mà chúng ta đặt ra nên sẽ trả lại lỗi đúng như các điều kiện mà nó đã vi phạm. Tuy nhiên trong trường hợp bạn muốn với mỗi field ta sẽ dừng lại không kiểm tra điều kiện tiếp theo nếu điều kiện trước đó lỗi thì ta cần thêm điều kiện bail ở đầu danh sách điều kiện của mỗi field như sau:

public function store(Request $request)
{
    $validatedData = $request->validate([
        'username' => 'bail|required|alpha|min:6|max:10',
        'email' => 'bail|required|email',
        'password' => 'bail|required|min:8',
        'password_confirmation' => 'bail|required|same:password',
    ]);
}

Khi đã thêm điều kiện bail cho mỗi field thì cùng với form nhập liệu lỗi ngay phía trên thì kết quả mà ta thu được cũng sẽ như sau:

Như bạn thấy username giờ chỉ báo lỗi do ta nhập cả số và kí tự chứ không còn báo lỗi về số kí tự tối thiểu như trước nữa do khi vi phạm điều kiện alpha thì field này được dừng lại không tiếp tục kiểm tra các lỗi khác nữa. Để thực sử dụng các điều kiện một cách chính xác và hợp lý thì trước khi dùng bạn nên tham khảo lại document để xem cách dùng cũng như những gì đã được hỗ trợ sẵn.

2. Custom validation

Trong trường hợp bạn không muốn dùng hàm validate() được cung cấp bởi class Request và muốn tự mình quyết định khi có lỗi sẽ thực hiện hành động gì hoặc chuyển hướng đi đâu thì thứ bạn cần chính là sử dụng Validator facades. Về cú pháp và cách dùng cùng tương tự như cách làm trên:

public function store(Request $request)
{
    $validator = Validator::make($request->all(), [
        'username' => 'bail|required|alpha|min:6|max:10',
        'email' => 'bail|required|email',
        'password' => 'bail|required|min:8',
        'password_confirmation' => 'bail|required|same:password',
    ]);

    if ($validator->fails()) {
        // Do something
    }
}

Lưu ý: bạn sẽ cần phải thêm use Validator; ở đầu controller để thực hiện chức năng này Ở đây, thay vì ta kiểm tra dữ liệu từ request thì ta sẽ sử dụng Validator facades và gọi đến chức năng make() với tham số truyền vào đầu tiên là tất cả các field trong request thông qua $request->all() và tham số thứ 2 là mảng chứa các điều kiện cần kiểm tra. Validator::make() sẽ chịu trách nghiệm kiểm tra các field sau đó tạo trả về một instance của class Validator và ta sẽ lưu vào biến validator. Từ biến này ta có thể gọi đến hàm fails() để kiểm tra xem có điều kiện kiểm tra nào không hợp lệ hay không rồi từ đó có thể đưa ra cách xử lý như mong muốn.

3. Form Request Validation

Bằng cách sử dụng 2 phương pháp trên chắc chắn sẽ tiết kiệm cho bạn rất nhiều công sức trong việc kiểm tra dữ liệu người dùng cũng như làm gọn lại function của mình. Tuy nhiên vẫn có những lúc bạn cảm thấy rằng việc thêm dù chỉ là vài dòng code liên quan đến việc kiểm tra dữ liệu người dùng trong function của controller là vẫn dài và hơn nữa controller cũng không cần phải biết đến việc này thì bạn có thể tách việc kiểm tra dữ liệu bằng cách tạo một class riêng cho nó cùng với một hàm kiểm tra cho form tương ứng và gọi nó trong controller của mình, việc đó có thể làm như sau:

<?php

namespace App\Validations;

class Validation
{
    public static function validateSignupRequest($request)
    {
        return $request->validate([
            'username' => 'bail|required|alpha|min:6|max:10',
            'email' => 'bail|required|email',
            'password' => 'bail|required|min:8',
            'password_confirmation' => 'bail|required|same:password',
        ]);
    }
}

Ta cũng có thể làm tương tự với Validator::make() Sau đó ta chỉ việc thêm class mới này vào controller bằng cách use App\Validations\Validation; và sau đó sử dụng như sau:

public function store(Request $request)
{
   Validation::validateSignupRequest($request);
}

Cách làm trên sẽ cho ta kết qua tương tự như ta mong đợi. Tuy nhiên, thay vì phải tự tạo class mới như vậy thì Laravel cung cấp sẵn cho chúng ta một phương pháp tương tự nhưng đơn giản và thậm chí ngắn gọn hơn nhiêu khi sử dụng trong controller đó là sử dụng FromRequest. Để tạo một class riêng cho một form dữ liệu ta dùng lệnh sau trên terminal (command line):

$ php artisan make:request <className>

Ở đây ta sẽ sử dụng class là SignupRequest và sau khi thực hiện lệnh trên Laravel sẽ tạo cho chúng ta một folder mới tên là file mới nằm trong folder app\Http'Controllers\Requests có tên là SignupRequest.php và có nội dung như sau:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SignupRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Class này gồm có 2 hàm chính: - authorize(): dùng để xác định xem người dùng nào có quyền thực hiện request này. Tạm thời chúng ta sẽ không quan tâm đến nó và để nó true tương ứng với ai cũng có thể thực hiện request này - rules(): đây là nơi định nghĩa quy định cho các field của chúng ta, ta sẽ sửa lại bằng cách copy phần validate ở trên vào đây:

public function rules()
{
    return [
        'username' => 'bail|required|alpha|min:6|max:10',
        'email' => 'bail|required|email',
        'password' => 'bail|required|min:8',
        'password_confirmation' => 'bail|required|same:password',
    ];
}

Cuối cùng ở hàm store() bên controller ta thay thế Request mặc định truyền vào bằng SignupRequest như sau:

public function store(SignupRequest $request)
{
  // Do something
}

Lúc này hàm store() của bạn sẽ được thực hiện khi toàn bộ các điều kiện đối với các filed trong class SignupRequest được thỏa mãn. Vì thể ở hàm này bạn chỉ cần lo code chức năng lưu trữ của nó chứ không còn cần viết phần kiểm tra dữ liệu nữa. Tất nhiền bạn cần thêm use App\Http\Controllers\Request\SignupRequest ở dầu controller để sử dụng được class này. Với cách làm này, code bạn đã trở nên tối ưu và ngắn gọn hơn rất nhiều so với phần code khi chúng ta bắn đầu.

Kết bài

Mong ra qua bài viết này bạn hiểu hơn về cách kiểm tra dữ liệu đầu vào từ client do Laravel cung cấp. Bài viết tiếp theo sẽ nói về cách lấy và sử dụng các lỗi sau khi kiểm tra, bạn hãy chú ý đón đọc.