Toidicode.com

Toidicode.com

BASIC TO ADVANCE

Bài 10: Validation trong Laravel 13

Ở bài trước form tạo project của TaskFlow vẫn kiểm tra dữ liệu bằng một câu if tự viết. Tiếp tục với series, bài này mình sẽ giới thiệu với mọi người về validation trong Laravel 13, một phần không thể thiếu của mọi form.

1. Validation với phương thức validate().

Validation là việc kiểm tra dữ liệu người dùng gửi lên có đúng yêu cầu hay không trước khi xử lý. Cách nhanh nhất là gọi validate() của request với cú pháp:

$request->validate($rules, $messages, $attributes);

Trong đó:

  • $rules là mảng với key là tên input, value là các rule cần kiểm tra.
  • $messages và $attributes là thông báo lỗi và tên hiển thị tùy biến, có thể bỏ trống (phần 4).

VD: Mình thay câu if trong ProjectController::store bằng validate().

public function store(Request $request): RedirectResponse
{
    $data = $request->validate([
        'name' => 'required|string|max:100',
        'description' => 'nullable|string|max:1000',
    ]);

    $project = $this->projects->add($data['name'], $data['description'] ?? null);

    // redirect như cũ ...
}

Dữ liệu hợp lệ thì validate() trả về mảng các input đã được kiểm tra và code chạy tiếp. Nếu không, Laravel ném ra ValidationException và tự tạo response: với form thông thường là redirect về trang trước, kèm lỗi và input cũ được flash vào session; với request cần JSON là response 422 chứa danh sách lỗi:

curl -s -X POST http://localhost:8000/projects -d "name=" \
    -H "Accept: application/json" -H "Sec-Fetch-Site: same-origin"
# {"message":"The name field is required.","errors":{"name":["The name field is required."]}}

Header Sec-Fetch-Site chỉ để vượt qua lớp chống CSRF như ở Bài 9.

2. Các rule thường dùng.

Các rule viết dạng chuỗi cách nhau bởi dấu | hoặc dạng mảng, 'required|string|max:100' tương đương ['required', 'string', 'max:100']. Dạng mảng bắt buộc khi dùng rule là object như Rule::in() bên dưới. Một số rule hay dùng:

  • required: bắt buộc có và không rỗng. nullable: cho phép null.
  • string, email, date: đúng kiểu chuỗi, email, ngày tháng.
  • min:3, max:255: với chuỗi là số ký tự, với số (khi có rule numeric hoặc integer) là giá trị.
  • in:todo,doing,done: chỉ nhận một trong các giá trị liệt kê.
  • after_or_equal:today: ngày phải từ hôm nay trở đi.
  • confirmed: phải trùng với input {tên}_confirmation, VD password với password_confirmation.

VD: Rule cho một form tạo task.

use Illuminate\Validation\Rule;

$request->validate([
    'title' => ['required', 'string', 'min:3', 'max:255'],
    'status' => ['required', Rule::in(['todo', 'doing', 'done'])],
    'due_date' => ['nullable', 'date', 'after_or_equal:today'],
    'email' => ['nullable', 'email'],
]);

// title=ab&status=pending&due_date=2020-01-01&email=abc
// The title field must be at least 3 characters.
// The selected status is invalid.
// The due date field must be a date after or equal to today.
// The email field must be a valid email address.

Vì Laravel đổi chuỗi rỗng thành null (Bài 8), input không bắt buộc phải có nullable, nếu không thì due_date để trống sẽ bị báo "must be a valid date".

Rule::in() nhận một mảng thay vì phải tự nối chuỗi. Tương tự, khi có enum TaskStatus ở Bài 19, các bạn dùng Rule::enum(TaskStatus::class). Các rule cần database như unique, exists mình sẽ dùng sau Bài 12. Danh sách đầy đủ các bạn xem tại đây.

3. Hiển thị lỗi trong Blade.

Middleware ShareErrorsFromSession trong nhóm web luôn chia sẻ biến $errors cho mọi view, nên không cần kiểm tra biến có tồn tại không. VD liệt kê tất cả lỗi:

@if ($errors->any())
    <ul>
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif

Đẹp hơn là hiện lỗi ngay dưới từng ô nhập bằng directive @error, bên trong có biến $message. Mình sửa form tạo project, bỏ khối session('error') cũ:

- resources/views/projects/create.blade.php

<div>
    <input type="text" name="name" value="{{ old('name') }}" placeholder="Tên project"
        class="w-full rounded border p-2 @error('name') border-red-500 @enderror">

    @error('name')
        <p class="mt-1 text-sm text-red-600">{{ $message }}</p>
    @enderror
</div>

Ô description làm tương tự. Input cũ được flash tự động nên old() vẫn điền lại giá trị đã nhập.

4. Thông báo lỗi tiếng Việt.

Tham số thứ hai của validate() là thông báo riêng theo dạng input.rule, tham số thứ ba là tên hiển thị thay cho :attribute trong thông báo:

$request->validate([
    'name' => 'required|string|max:100',
], [
    'name.required' => 'Các bạn chưa nhập tên project.',
], [
    'name' => 'tên project',
]);

// name rỗng: Các bạn chưa nhập tên project.
// name dài 101 ký tự: The tên project field must not be greater than 100 characters.

Thông báo mặc định vẫn là tiếng Anh. Để dịch, các bạn tạo thư mục lang bằng lệnh sau, nó sẽ chép các file auth.php, pagination.php, passwords.php, validation.php vào lang/en:

php artisan lang:publish

Rồi tạo lang/vi/validation.php với cùng key như lang/en/validation.php. Mình chỉ dịch các rule TaskFlow đang dùng:

- lang/vi/validation.php

<?php

return [
    'required' => 'Trường :attribute là bắt buộc.',
    'string' => 'Trường :attribute phải là chuỗi.',
    'max' => [
        'string' => 'Trường :attribute không được dài quá :max ký tự.',
    ],
];

Và đổi ngôn ngữ của ứng dụng trong .env:

APP_LOCALE=vi

Lúc này lỗi max ở trên thành "Trường tên project không được dài quá 100 ký tự.". Rule nào chưa dịch sẽ lấy thông báo tiếng Anh của APP_FALLBACK_LOCALE (en).

5. Form Request.

Khi rule nhiều lên, controller sẽ rối. Form Request là class request riêng chứa toàn bộ phần validation, tạo bằng lệnh:

php artisan make:request StoreProjectRequest

File được tạo trong app/Http/Requests, mình chuyển rule ở trên vào:

- app/Http/Requests/StoreProjectRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;

class StoreProjectRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        // tạm cho phép tất cả, phân quyền sẽ có ở Bài 22
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:100'],
            'description' => ['nullable', 'string', 'max:1000'],
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'Các bạn chưa nhập tên project.',
        ];
    }

    public function attributes(): array
    {
        return [
            'name' => 'tên project',
            'description' => 'mô tả',
        ];
    }
}

Trong đó:

  • authorize() quyết định người dùng có được gửi request này không, trả về false sẽ nhận lỗi 403. File mới tạo trả về false, nhớ đổi thành true.
  • rules(), messages(), attributes() tương ứng ba tham số của validate().

Trong controller, chỉ cần khai báo kiểu tham số là StoreProjectRequest, request sẽ được kiểm tra trước khi vào method:

- app/Http/Controllers/ProjectController.php

use App\Http\Requests\StoreProjectRequest;

public function store(StoreProjectRequest $request): RedirectResponse
{
    // chỉ chạy đến đây khi dữ liệu đã hợp lệ
    $data = $request->validated();

    $project = $this->projects->add($data['name'], $data['description'] ?? null);

    return to_route('projects.show', ['id' => $project['id']])
        ->with('status', 'Đã tạo project "'.$project['name'].'".');
}

validated() chỉ trả về các input có trong rule. Gửi thêm is_admin=1 hay _token thì kết quả vẫn chỉ là ['name' => '...', 'description' => ...]. Thói quen này rất quan trọng ở Bài 15, khi chúng ta lưu thẳng mảng dữ liệu vào model: dùng all() thì người dùng có thể ghi cả những cột không được phép.

Lúc này gửi form với tên trống, dưới ô nhập hiện "Các bạn chưa nhập tên project.", mô tả đã nhập vẫn còn.

6. Lời kết.

Như vậy chúng ta đã tìm hiểu xong validation trong Laravel 13: validate(), các rule thường dùng, hiển thị lỗi trong Blade, thông báo tiếng Việt và Form Request. Các bạn có thể xem thêm tại tài liệu chính thức. ValidationException chỉ là một trong nhiều exception mà Laravel tự xử lý, bài sau mình sẽ giới thiệu về xử lý lỗi và logging, trang lỗi tùy biến và cách ghi log.

Đăng ký nhận tin.

Chúng tôi chỉ gửi tối đa 2 lần trên 1 tháng. Tuyên bố không spam mail!

Vũ Thanh Tài

About author
The best way to learn is to share
Xem tất cả bài đăng

0 Comments

Bài viết chưa có ai bình luận, hãy là người đầu tiên đi bạn!

Bình luận

Captcha