post-image

Validation trong Laravel (Phần 3)

Tổng quan

Mở đầu

Trong hai bài viết trước của serie, mình đã giới thiệu với các bạn về vấn đề kiểm tra tính đúng đắn của dữ liệu nhập vào do người dùng cung cấp, cụ thể là cách kiểm trả dữ liệu và hiển thị lại lỗi cho người dùng. Ở bài viết này, mình sẽ hướng dẫn các bạn cách tạo ra những điều kiện dùng để kiểm tra dữ liệu do chính các bạn định nghĩa. 

Vấn đề

Laravel mặc định đã cung cấp cho chúng ta rất nhiều các điều kiện có thể sử dụng để kiểm tra dữ liệu đến.

0d61afed 39d5 4a1d 90ef 48878014e78e

Tuy nhiên trong thực tế, không phải chỉ nhữngđiều kiện này là đủ cho project của chúng ta mà chúng ta có những điều kiện riêng cho project. Laravel cũng đã tính toán tới vấn đề này vì thế nó đã cung cấp cho chúng ta một số cách để tạo ra các điều kiện mới theo yêu cầu của cá nhân.

Custom validation rules

Giả sử chúng ta có 2 field cần kiểm trả lần lượt là:

<input type='text' name='name'> <input type='text' name='number'>
Code language: HTML, XML (xml)

Và chúng ta muốn validation 2 field trên với điều kiện như sau:

  • name:
    • Không được để trống
    • Tối thiểu 6 kí tự
    • Tất cả các từ phải viết hoa
  • number:
    • Không được để trống
    • Là số
    • Tổng của các chữ cái trong name + number phải là số chẵn

Đây là form của chúng ta:

a6e367b6 43f9 4d14 afd4 df744d3d459f

Chúng ta sẽ sử dụng FormRequest như trong bài viết trước đấy để validate form trên. Còn đây là những điều kiện cơ bản của Laravel mà ta dùng để validate 2 field trên:

public function rules() { return [ 'name' => 'required|size:6', 'number' => 'required|numeric', ]; }
Code language: PHP (php)

Với nội dung điều kiện như trên, ta đã có thể thỏa mãn 2 điều điện đầu tiên của field name và điều kiện thứ nhất của filed number Để thực hiện việc validate tất cả các chữ viết hoa hay tổng các chứ của name và number chia hết cho 5, ta cần phải tự định nghĩa điều kiện này. Dưới đây là một số phương pháp mà Laravel cung cấp cho việc tự định nghĩa điều kiện:

1. Sử dụng Closures

Nếu học PHP chắc hẳn bạn đã biết đến khái niệm Closure, để định nghĩa một điều kiện sử dụng Closure, ta cần sửa lại phần điều kiện của các field thành một array chứ không phải một string như ở trên, việc sửa đổi như sau:

public function rules() { return [ 'name' => [ 'required', 'size:6', ], 'number' => [ 'required', 'numeric' ] ]; }
Code language: PHP (php)

Tiếp đến ta thêm một Closure có dạng như sau vào cả 2 field:

function ($attribute, $value, $fail) { }
Code language: PHP (php)

Closure trên gồm có 3 biến mặc định là:

  • $attribute: chính là tên của field cần validate tương ứng, ở đây sẽ lần lượt là name và number.
  • $value: là giá trị nhận vào khi người dùng submit form.
  • $fail: là một callback được gọi đến khi việc validate thất bại. Đây chính là nơi mà bạn có thể truyền vào thông báo khi việc validate thất bại.

Sau khi thêm Closure vào điều kiệncủa chúng ta sẽ có dạng như sau:

public function rules() { return [ 'name' => [ 'required', 'size:6', function ($attribute, $value, $fail) { } ], 'number' => [ 'required', 'numeric' function ($attribute, $value, $fail) { } ] ]; }
Code language: PHP (php)

Đầu tiên chúng ta sẽ tiến hành định nghĩa điều kiện yêu cầu cho field name phải được nhập vào dưới dạng uppercase như sau:

function ($attribute, $value, $fail) { if (strtoupper($value) !== $value) { return $fail("The $attribute must be upper case"); } }
Code language: PHP (php)

Như bạn thấy ở trên, ta chỉ việc sử dụng hàm strtoupper() có sẵn trong PHP để chuyển $value người dùng nhập vào sang dạng viết hoa và so sánh nó với $value gốc. Trường hợp $value của người dùng nhập vào là viết hoa thì việc validate là thành công và không có lỗi gì.

Còn trong trường hợp ngườ dùng nhập không phải chữ in hoa sẽ lập tức dẫn đến điều kiện if đúng và sẽ chạy hàm $fail() và trả lại lỗi. Ở đây trong hàm $fail() ta có thể dùng $attribute để tạo thông báo về lỗi cho người dùng. Sau đó ta chạy thử submit lại form với điều kiện vừa nhập sẽ thu được kết quả như sau:

Trường hợp để form trống:

2ee88d9

Trường hợp nhập nội dung cả 2 field như name < 6 kí tự và không viết hoa:

19
  • Nhập name > 6 kí tự nhưng không viết hoa:!
  • Nhập name thỏa mãn các điều kiện:
bcf5db

Như vậy ta đã tạo được điều kiện thứ nhất yêu cầu toàn bộ nội dung field name nhập vào phải là uppercase. Tiếp đến với field number ta không những cần $value của chính nó mà còn cần cả $value của field name. Để thực hiện điều đó, ta làm như sau:

function ($attribute, $value, $fail) { if ((strlen($this->name) + $value) % 2 != 0) { return $fail("Sum of $attribute and name's total chars must be an even number"); } }
Code language: PHP (php)

Mặc định trong FormRequest ta có thể truy cập đến giá trị của các field khác thông qua từ khóa $this->[name_of_the_field]. Chính vì thể ở Closure trên ta có thể dùng $this->name để lấy giá trị field name rồi dùng để tính tổng và kiểm tra xem có phải số chăn không. Ta có thể thử nghiệm lại điều kiện vừa tạo như sau:

  • Để trống name và nhập number là số lẻ:
cdv

Như ta thấy 3 là số lẻ và name để trống nên tổng là 3 và là số lẻ nên validate fail.

  • Để trống name và nhập number là số chẵn:
chanb

Ở đây tổng là 4 là số chẵn nên validate thành công.

  • Cuối cùng ta thử nhập cả name và number sao cho tổng thỏa mãn:
f7vsdvsdvvv 7ca12c56332d

Như vậy với name gồm 6 ký tự và number bằng 4 ta thu được tổng 10 là số chẵn nên thỏa mãn điều kiện của điều kiện ta vừa tạo. Trường hợp nhập tổng name và number lẻ sẽ xuất hiện lỗi:

642a8897 e853 48bvevefvefveveveveevvef5 a12e 63048d9d6ffd

Đây là kết quả cuối cùng chúng ta thu được trong hàm rules() của FormRequest:

public function rules() { return [ 'name' => [ 'required', 'min:6', function ($attribute, $value, $fail) { if (strtoupper($value) !== $value) { return $fail("The $attribute must be upper case"); } }, ], 'number' => [ 'required', 'numeric', function ($attribute, $value, $fail) { if ((strlen($this->name) + $value) % 2 != 0) { return $fail("Sum of $attribute and name's total chars must be an even number"); } } ] ]; }
Code language: PHP (php)

Với cách sử dụng Closure như ở trên, bạn đã có thể tự tạo ra những rule riêng phù hợp với project của mình tuy nhiên, trong trường hợp cùng một rule bạn tạo với Closure nhưng được sử dụng ở nhiều nơi khác nhau thì cách làm trên có vẻ không ổn cho lắm. Nếu chẳng may, bạn có thay đổi rule sẽ phải đi đến từng vị trí bạn copy Closure như ở ví dụ trên để sửa rất mất công. Chính vì thế, Laravel còn cung cấp cho chúng ta cách làm khác để giải quyết vấn đề này là Rule Object. Tuy nhiên Rule Object,

2. Rule Object

Rule Object là một class mà trong đó bạn có thể định nghĩa các custom rule của bạn tương tự như Closure và có thể gọi đến instance của class rule đó ở bất cứ đâu bạn cần. Nếu có thay đổi, bạn chỉ cần mở lại class đó lên và chỉnh sửa thì tất cả các vị trí sử dụng instance của class sẽ được cập nhật theo. Để tạo một Rule Object, ta sử dụng cú pháp sau:

$ php artisan make:rule [class_name]

Ở đây mình sẽ làm ví dụ về cách tạo Rule Object cho rule của field number. Còn lại field name bạn hãy dựa vào ví dụ của mình và tự làm theo để nắm rõ hơn cách sử dụng. Ta tiến hành tạo Rule Object với cú pháp nói trên:

$ php artisan make:rule CustomEvenNumber

Xong khi gõ lệnh trên, trong folder app của bạn sẽ xuất hiện một thư mục mới là Rules, bên trong đó sẽ chứa các class mà bạn dùng để định nghĩa các rule:

7eab35ca 7b58 4044 978e 131402afd457

Đây là nội dung bên trong của class CustomEvenNumber:

<?php namespace App\Rules; use Illuminate\Contracts\Validation\Rule; class CustomEvenNumber implements Rule { public function __construct() { // } public function passes($attribute, $value) { // } public function message() { return 'The validation error message.'; } }
Code language: HTML, XML (xml)

Class này gồm có 3 hàm hình:

  • __construct(): hàm khởi tạo của rule, nơi ta có thể truyền thêm các biến khác vào
  • passes(): hàm để định nghĩa giá trị của filed cần valite có thỏa mãn hay không (2 biến $attribute và $value có giá trị tương tự như sử dụng Closure)
  • mesage(): thông báo trả về nếu điều kiện không thỏa mãn

Vì điều kiện ta cần tạo yêu cầu giá trị của field name, nên ta sẽ tiến hành truyền giá trị đó vào thông qua hàm __construct() như sau:

protected $name; public function __construct($name) { $this->name = $name; }
Code language: PHP (php)

Tiếp đó trong hàm passes() ta sẽ định nghĩa nội dung tương tự với cách làm Closure nói trên. Tuy nhiên bạn nên chú ý rằng với cách sử dụng Closure ta sẽ định nghĩa điều kiện dẫn đến việc validate thất bại còn trong hàm passes() ta định nghĩa điều kiện validate thành công -> nội dung của Closure và Rule Object là ngược nhau. Vì thế nội dung hàm passes() như sau:

public function passes($attribute, $value) { return (strlen($this->name) + $value) % 2 == 0; }
Code language: PHP (php)

Còn nội dung của Closure:

function ($attribute, $value, $fail) { if ((strlen($this->name) + $value) % 2 != 0) { return $fail("Sum of $attribute and name's total chars must be an even number"); } }
Code language: PHP (php)

Bạn có thể thấy 2 điều kiện khác nhau, một là khác 0 còn một là bằng 0. Cuối cùng, trong hàm message() ta copy lại nội dung từ bên Closure và sửa lại một chút như sau:

public function message() { return "Sum of :attribute and name's total chars must be an even number"; }
Code language: PHP (php)

Đây là nội dung hoàn chỉnh của class CustomEventNumber:

<?php namespace App\Rules; use Illuminate\Contracts\Validation\Rule; class CustomEvenNumber implements Rule { protected $name; public function __construct($name) { $this->name = $name; } public function passes($attribute, $value) { return (strlen($this->name) + $value) % 2 == 0; } public function message() { return "Sum of :attribute and name's total chars must be an even number"; } }
Code language: HTML, XML (xml)

Quay lại bên FormReques ta sẽ xóa phần Closure của field number và thay class CustomEvenNumber như sau:

'number' => [ 'required', 'numeric', new CustomEvenNumber($this->name), ]
Code language: PHP (php)

Tương tự như trong Closure ta cũng phải truyền vào $this->name là giá trị của field name. Bạn nhớ use class vừa tạo ở đầu class FormRequest:

use App\Rules\CustomEvenNumber;
Code language: PHP (php)

Cuối cùng, đây là nội dung hoàn chỉnh của FormRequest:

<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use App\Rules\CustomEvenNumber; class CustomRequest extends FormRequest { public function authorize() { return true; } public function rules() { return [ 'name' => [ 'required', 'min:6', function ($attribute, $value, $fail) { if (strtoupper($value) !== $value) { return $fail("The $attribute must be upper case"); } }, ], 'number' => [ 'required', 'numeric', new CustomEvenNumber($this->name), ] ]; } }
Code language: HTML, XML (xml)

Lưu ý: Khi dùng Rule Object hay Closure bạn đều có thể sử dụng Eloquent để truy cập tới CSDL.

Kết bài

Đây là bài viết thứ 3 cũng là bài viết cuối cùng của mình về việc validate dữ liệu trong Laravel. Cảm ơn các bạn đã đọc.

Các bạn có thể tham khảo các bài viết hay về Laravel tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Tham khảo: Viblo

Leave a Reply

Your email address will not be published. Required fields are marked *