Viblo Learning
+4

Cách validation form hiệu quả trong Laravel

Cách hay dùng(không tốt):

Hiện tại có lẽ rất nhiều bạn vẫn đang validate kiểu thế này:

<?php
class SessionsController extends BaseController {

    public function store()
    {
        $formData = Input::only('email', 'password');
        $rules = [
            'email' => 'required|email',
            'password' => 'required'
        ];

        $validator = \Validator::make($formData, $rules);

        if ($validator->fails()) {
            return Redirect::back()->withInput()->withErrors($validator->errors());
        }

        // Do something.
        // Example: Login, etc.
        return Redirect::route('users.profile');
    }
}

Cách này dùng được nhưng ko phải là hay vì bạn sẽ phải lặp lại việc validate ở rất nhiều nơi, và điều đó thì đi ngược lại với nguyên tắc lập trình cụ thể ở đây là DRY(Don't Repeat Yourself)

Vậy cách tốt hơn là gì?

Mục tiêu của chúng ta sẽ là tách phần validation ra khỏi Controller và đặt vào 1 nơi "chung chung" nào đó để tăng tính maintainable và reusable. Nơi đó sẽ là 1 base class mà tất cả các form khác cần validate sẽ extends nó. Ví dụ với Login và Register Form:

class Login extends BaseForm
{

}
class Register extends BaseForm
{

}

Ta là như vậy để tăng tính DRY của code, khi đó các thao tác chung sẽ được viết trong BaseForm và các thao tác riêng được định nghĩa ở các form tương ứng. Bây giờ chúng ta sẽ triển khai phần BaseForm:

abstract class BaseForm
{
    /**
     * @var Validator
     */
    protected $validation;

    /**
     * @var \Illuminate\Validation\Factory
     */
    private $validator;

    /**
     * @param \Illuminate\Validation\Factory $validator
     */
    public function __construct(ValidatorFactory $validator)
    {
        $this->validator = $validator;
    }

    /**
     * @param array $formData
     *
     * @throws FormValidationException
     */
    public function validate(array $formData)
    {
        // Instantiate validator instance by factory
        $this->validation = $this->validator->make($formData, $this->rules());

        // Validate
        if ($this->validation->fails()) {
            throw new FormValidationException('Validation Failed', $this->getValidationErrors());
        }

        return true;
    }

    /**
     * @return MessageBag
     */
    protected function getValidationErrors()
    {
        return $this->validation->errors();
    }

    /**
     * @return array
     */
    abstract protected function rules();
}

Điều đầu tiên, class của chúng ta là abstract vì nó ko phải là 1 class hoàn chỉnh và ta ko muốn nó được khởi tạo. Tiếp đó hãy cùng nhìn vào hàm validate, hàm này thực chất tương đương với việc bạn gọi Validate::make() của Laravel nhưng chúng ta đã viết nó 1 cách abstract hơn, ta ko gọi thằng Validate::make() của Laravel mà khai báo 1 ValidatorFactory, với việc dùng Dependency Injection ta sẽ bind validator vào sau nếu không muốn dùng mặc định của Laravel mà có thể là tự viết hoặc của hãng thứ 3 nào đó.

Tiếp đó sau khi khởi tạo Validator ta gọi phương thức make() để validate với tham số $this->rules() được truyền vào, phần $this->rules() này được định nghĩa là abstract để các form kế thừa có thể tự định nghĩa các rule của mình.

Bước cuối cùng ta kiểm tra xem validate có thành công không, nếu có chỉ đơn giản trả về true, còn nếu fail thì ta sẽ throw ra 1 Excecption với các errors được lấy từ hàm: getValidationErrors(), hàm này đơn giản lấy các errors từ validation thôi.

Tiếp theo ta cần định nghĩa 1 Custom Exception để làm rõ ràng hơn nội dung lỗi sẽ được throw ra:

class FormValidationException extends \Exception
{
    /**
     * @var MessageBag
     */
    protected $errors;

    /**
     * @param string     $message
     * @param MessageBag $errors
     * @param int        $code
     * @param Exception  $previous
     */
    public function __construct($message = "", MessageBag $errors, $code = 0, Exception $previous = null)
    {
        $this->errors = $errors;

        parent::__construct($message, $code, $previous);
    }

    public function getErrors()
    {
        return $this->errors;
    }
}

Giờ ta đã có thể khai báo rules ở các form:

class LoginForm extends BaseForm
{
    /**
     * @return array
     */
    protected function rules()
    {
        return [
            'email'    => 'required|email',
            'password' => 'required|alpha'
        ];
    }
}
class RegisterForm extends BaseForm
{
    /**
     * @return array
     */
    protected function rules()
    {
        return [
            'full_name'             => 'alpha',
            'email'                 => 'required|email',
            'password'              => 'required|alpha|between:8,32',
            'password_confirmation' => 'required|alpha|confirmed',
        ];
    }
}

Ở trong Controller lúc này chúng ta sẽ dùng các Form đã tạo:

class SessionsController extends BaseController {

    protected $loginForm;

    public function __construct(LoginForm $loginForm)
    {
        $this->loginForm = $loginForm;
    }

    public function store()
    {
        $formData = Input::only('email', 'password');

        try {
            // Validate
            $this->loginForm->validate($formData);
        } catch (FormValidationException $e) {
            return Redirect::back()->withInput()->withErrors($e->getErrors());
        }
        // Do something.
        // Example: Login...
        return Redirect::route('users.profile');
    }
}

Ở đây ta try/catch phần validate của form và dùng phương thức getErrrors() đã được định nghĩa trong FormValidationException để lấy về các thông báo lỗi và Redirect, rất tiện phải không 😄

Vậy còn thông báo lỗi thì sao nhỉ? Bạn sẽ muốn custom các message thông báo lỗi => làm thêm 1 method messages() trong BaseForm vàimplement ở các class kế thừa

abstract protected function messages();

Trong các class kế thừa:

protected function messages()
    {
        return [
            'alpha'             => 'Họ tên phải là ký tự',
            'email.required'    => 'Email chưa nhập',
        ];
    }

Và sửa lại 1 chút ở phần validate của BaseForm:

$this->validation = $this->validator->make($formData, $this->rules(), $this->messages());

Kết luận:

Trên đây là 1 cách tốt hơn để validate trong Laravel, bạn có thể tham khảo hay tự viết ra cách tốt nhất cho riêng mình, miễn sao đảm bảo được tính dễ bảo trì và tái sử dụng code.

Chúc các bạn thành công!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.