NTM Solutions

Thứ Ba, 18 tháng 8, 2026

📘 BÀI 29 — SEARCH, SORT, FILTER, PAGINATION & DATATABLES

Trong các bài trước, chúng ta đã xây dựng được các chức năng CRUD cho:

  • Categories

  • Posts

  • Users

Tuy nhiên, khi dữ liệu bắt đầu nhiều lên, một bảng kiểu:

User::all();

sẽ nhanh chóng trở nên bất tiện.

Ví dụ hệ thống có 10.000 User:

  • Không thể hiển thị tất cả trên một trang.

  • Người quản trị cần tìm User theo tên/email.

  • Cần sắp xếp theo tên, ngày đăng ký...

  • Cần lọc theo Role.

  • Cần phân trang.

  • Và nếu dữ liệu lớn, nên để Database xử lý Search / Sort / Filter / Pagination thay vì tải toàn bộ dữ liệu về trình duyệt.

Trong Laravel 13, Query Builder/Eloquent hỗ trợ trực tiếp các cơ chế phân trang như paginate(), simplePaginate()cursorPaginate(). (Laravel API)



🎯 Mục tiêu bài học

Sau bài này, chúng ta sẽ xây dựng trang:

Dashboard
└── Users
    ├── 🔎 Search
    ├── 🎯 Filter Role
    ├── ↕ Sort
    ├── 📄 Pagination
    └── 📊 DataTables

Người học sẽ hiểu được hai cách:

Cách 1 — Laravel Pagination

Browser
   ↓
Request
   ↓
Laravel
   ↓
MySQL
   ↓
paginate()
   ↓
Blade

Cách 2 — DataTables AJAX

Browser
   ↓
DataTables
   ↓ AJAX
Laravel
   ↓
MySQL
   ↓
JSON
   ↓
DataTables

Với dữ liệu rất lớn, DataTables có cơ chế server-side processing, tức là việc tìm kiếm, sắp xếp và phân trang được thực hiện phía server/database thay vì tải toàn bộ dữ liệu về browser. (DataTables Plus)


1. Chuẩn bị dữ liệu Users

Trong dự án Laravel 13 của chúng ta đã có bảng:

users

Ví dụ:

idnameemailrole
1Nguyễn Văn Aa@gmail.comadmin
2Trần Văn Bb@gmail.comuser
3Lê Văn Cc@gmail.comeditor

Model:

app/Models/User.php

Ví dụ:

class User extends Authenticatable
{
    protected $fillable = [
        'name',
        'email',
        'password',
        'role',
    ];
}

2. Tạo User Controller

Nếu chưa có:

php artisan make:controller UserController --resource

Laravel sẽ tạo:

app/Http/Controllers/UserController.php

Controller có các method:

index()
create()
store()
show()
edit()
update()
destroy()

Trong bài này chúng ta tập trung vào:

index()

3. Route

Mở:

routes/web.php

Thêm:

use App\Http\Controllers\UserController;

Route::resource('users', UserController::class);

Sau đó:

php artisan route:list

Chúng ta sẽ có:

GET       /users
POST      /users
GET       /users/create
GET       /users/{user}
GET       /users/{user}/edit
PUT       /users/{user}
DELETE    /users/{user}

Trang danh sách User:

/users

sẽ chạy:

UserController@index

4. Pagination cơ bản

Đầu tiên thử cách đơn giản nhất.

Trong:

UserController.php

viết:

use App\Models\User;

public function index()
{
    $users = User::latest()->paginate(10);

    return view('users.index', compact('users'));
}

Ý nghĩa:

paginate(10)

là:

Mỗi trang tối đa 10 User.

Ví dụ có 53 User:

Trang 1 → 10 User
Trang 2 → 10 User
Trang 3 → 10 User
Trang 4 → 10 User
Trang 5 → 10 User
Trang 6 → 3 User

Laravel cung cấp paginate() để tạo paginator đầy đủ thông tin tổng số bản ghi; ngoài ra còn có simplePaginate()cursorPaginate() cho những trường hợp phù hợp khác. (Laravel API)


5. Hiển thị Pagination trong Blade

Trong:

resources/views/users/index.blade.php

sau bảng:

<div class="mt-4">
    {{ $users->links() }}
</div>

Ví dụ:

<table class="w-full">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
        </tr>
    </thead>

    <tbody>
        @foreach ($users as $user)
            <tr>
                <td>{{ $user->id }}</td>
                <td>{{ $user->name }}</td>
                <td>{{ $user->email }}</td>
                <td>{{ $user->role }}</td>
            </tr>
        @endforeach
    </tbody>
</table>

<div class="mt-4">
    {{ $users->links() }}
</div>

6. Search User

Bây giờ chúng ta thêm ô tìm kiếm.

Giao diện:

┌─────────────────────────────────────────────┐
│ 🔎 Tìm User...                    [Search] │
└─────────────────────────────────────────────┘

Blade:

<form method="GET" action="{{ route('users.index') }}">
    <input
        type="text"
        name="search"
        value="{{ request('search') }}"
        placeholder="Tìm tên hoặc email..."
    >

    <button type="submit">
        Tìm kiếm
    </button>
</form>

Khi người dùng nhập:

nguyen

URL sẽ trở thành:

/users?search=nguyen

7. Xử lý Search trong Controller

Sửa:

public function index()
{
    $users = User::latest()->paginate(10);

    return view('users.index', compact('users'));
}

thành:

public function index()
{
    $query = User::query();

    if (request('search')) {
        $search = request('search');

        $query->where(function ($q) use ($search) {
            $q->where('name', 'like', "%{$search}%")
              ->orWhere('email', 'like', "%{$search}%");
        });
    }

    $users = $query
        ->latest()
        ->paginate(10)
        ->withQueryString();

    return view('users.index', compact('users'));
}

Đây là một pattern cực kỳ quan trọng:

$query = User::query();

Sau đó xây dựng query từng bước.


8. Vì sao dùng withQueryString()?

Nếu tìm:

nguyen

trang đầu tiên có URL:

/users?search=nguyen

Khi chuyển sang trang 2, chúng ta muốn:

/users?page=2&search=nguyen

chứ không phải:

/users?page=2

Vì vậy:

->withQueryString()

giúp giữ lại các query parameter hiện tại.


9. Search nhiều trường

Chúng ta có thể tìm:

name
email

bằng:

$query->where(function ($q) use ($search) {
    $q->where('name', 'like', "%{$search}%")
      ->orWhere('email', 'like', "%{$search}%");
});

SQL tương đương về ý tưởng:

WHERE
    name LIKE '%nguyen%'
    OR email LIKE '%nguyen%'

Đây là lý do phải sử dụng:

where(function () {})

để nhóm điều kiện OR lại với nhau.


10. Filter theo Role

Bây giờ thêm bộ lọc:

Role:

[ Tất cả       ▼ ]

[ admin        ]
[ editor       ]
[ user         ]

Blade:

<select name="role">
    <option value="">Tất cả Role</option>

    <option value="admin"
        @selected(request('role') === 'admin')>
        Admin
    </option>

    <option value="editor"
        @selected(request('role') === 'editor')>
        Editor
    </option>

    <option value="user"
        @selected(request('role') === 'user')>
        User
    </option>
</select>

11. Xử lý Filter

Trong Controller:

public function index()
{
    $query = User::query();

    // SEARCH
    if ($search = request('search')) {
        $query->where(function ($q) use ($search) {
            $q->where('name', 'like', "%{$search}%")
              ->orWhere('email', 'like', "%{$search}%");
        });
    }

    // FILTER ROLE
    if ($role = request('role')) {
        $query->where('role', $role);
    }

    // PAGINATION
    $users = $query
        ->latest()
        ->paginate(10)
        ->withQueryString();

    return view('users.index', compact('users'));
}

Bây giờ có thể:

Search:
nguyen

Role:
admin

URL:

/users?search=nguyen&role=admin

Database sẽ tìm:

name/email chứa "nguyen"
AND
role = admin

12. Sort

Bây giờ chúng ta muốn:

Sắp xếp:

Mới nhất
Cũ nhất
Tên A → Z
Tên Z → A

Blade:

<select name="sort">
    <option value="latest"
        @selected(request('sort', 'latest') === 'latest')>
        Mới nhất
    </option>

    <option value="oldest"
        @selected(request('sort') === 'oldest')>
        Cũ nhất
    </option>

    <option value="name_asc"
        @selected(request('sort') === 'name_asc')>
        Tên A → Z
    </option>

    <option value="name_desc"
        @selected(request('sort') === 'name_desc')>
        Tên Z → A
    </option>
</select>

13. Xử lý Sort

Controller:

switch (request('sort')) {

    case 'oldest':
        $query->oldest();
        break;

    case 'name_asc':
        $query->orderBy('name', 'asc');
        break;

    case 'name_desc':
        $query->orderBy('name', 'desc');
        break;

    default:
        $query->latest();
        break;
}

Sau đó:

$users = $query
    ->paginate(10)
    ->withQueryString();

14. Hoàn chỉnh UserController@index

Đến đây chúng ta có:

  • Search

  • Filter

  • Sort

  • Pagination

Code hoàn chỉnh:

public function index()
{
    $query = User::query();

    /*
    |--------------------------------------------------------------------------
    | SEARCH
    |--------------------------------------------------------------------------
    */

    if ($search = request('search')) {

        $query->where(function ($q) use ($search) {

            $q->where('name', 'like', "%{$search}%")
              ->orWhere('email', 'like', "%{$search}%");

        });
    }


    /*
    |--------------------------------------------------------------------------
    | FILTER ROLE
    |--------------------------------------------------------------------------
    */

    if ($role = request('role')) {
        $query->where('role', $role);
    }


    /*
    |--------------------------------------------------------------------------
    | SORT
    |--------------------------------------------------------------------------
    */

    switch (request('sort')) {

        case 'oldest':
            $query->oldest();
            break;

        case 'name_asc':
            $query->orderBy('name', 'asc');
            break;

        case 'name_desc':
            $query->orderBy('name', 'desc');
            break;

        default:
            $query->latest();
            break;
    }


    /*
    |--------------------------------------------------------------------------
    | PAGINATION
    |--------------------------------------------------------------------------
    */

    $users = $query
        ->paginate(10)
        ->withQueryString();


    return view('users.index', compact('users'));
}

Đây chính là cấu trúc mà chúng ta sẽ thường xuyên sử dụng trong các trang quản trị Laravel.


15. Gộp Search + Filter + Sort thành một Form

Blade:

<form
    method="GET"
    action="{{ route('users.index') }}"
    class="flex gap-2 mb-4"
>

    {{-- SEARCH --}}
    <input
        type="text"
        name="search"
        value="{{ request('search') }}"
        placeholder="Tìm tên hoặc email..."
    >


    {{-- ROLE --}}
    <select name="role">

        <option value="">
            Tất cả Role
        </option>

        <option
            value="admin"
            @selected(request('role') === 'admin')
        >
            Admin
        </option>

        <option
            value="editor"
            @selected(request('role') === 'editor')
        >
            Editor
        </option>

        <option
            value="user"
            @selected(request('role') === 'user')
        >
            User
        </option>

    </select>


    {{-- SORT --}}
    <select name="sort">

        <option
            value="latest"
            @selected(request('sort', 'latest') === 'latest')
        >
            Mới nhất
        </option>

        <option
            value="oldest"
            @selected(request('sort') === 'oldest')
        >
            Cũ nhất
        </option>

        <option
            value="name_asc"
            @selected(request('sort') === 'name_asc')
        >
            Tên A → Z
        </option>

        <option
            value="name_desc"
            @selected(request('sort') === 'name_desc')
        >
            Tên Z → A
        </option>

    </select>


    <button type="submit">
        🔎 Tìm kiếm
    </button>

</form>

Kết quả:

┌──────────────────────────────────────────────────────────┐
│ 🔎 Nguyễn       [Admin ▼] [Mới nhất ▼] [ Tìm kiếm ]     │
└──────────────────────────────────────────────────────────┘

16. Nút Reset

Nên có thêm:

<a href="{{ route('users.index') }}">
    Xóa bộ lọc
</a>

Khi click:

/users

toàn bộ:

search
role
sort

được xóa.


17. Pagination với Tailwind

Nếu dự án đang sử dụng Tailwind:

{{ $users->links() }}

Laravel có thể render pagination view tương ứng với frontend stack đã cấu hình.

Nếu cần tùy chỉnh giao diện, Laravel cho phép publish pagination views để chỉnh sửa.


18. Vấn đề của Pagination truyền thống

Cách trên hoàn toàn đủ cho nhiều dự án:

Search
+
Filter
+
Sort
+
Pagination

Nhưng khi làm Admin Dashboard, người dùng thường muốn giao diện giống:

┌───────────────────────────────────────────────────────────┐
│ Users                                      [+ Add User]   │
├───────────────────────────────────────────────────────────┤
│ Search [____________] Role [____]                        │
├────┬──────────────┬────────────────┬─────────┬───────────┤
│ ID │ Name         │ Email          │ Role    │ Action    │
├────┼──────────────┼────────────────┼─────────┼───────────┤
│ 1  │ Nguyễn Văn A │ a@gmail.com    │ Admin   │ Edit      │
│ 2  │ Trần Văn B   │ b@gmail.com    │ User    │ Edit      │
│ 3  │ Lê Văn C     │ c@gmail.com    │ Editor  │ Edit      │
└────┴──────────────┴────────────────┴─────────┴───────────┘

Showing 1 to 10 of 245 users

              1  2  3  4  5  Next

Đây là lúc DataTables trở nên hữu ích.


19. DataTables là gì?

DataTables.net là thư viện JavaScript dùng để biến HTML <table> thành một bảng dữ liệu tương tác.

Nó cung cấp nhiều chức năng:

Search
Sort
Pagination
Ajax
Responsive
Column ordering
Filtering
Server-side processing

Đặc biệt:

serverSide: true

cho phép DataTables gửi yêu cầu lên server mỗi khi người dùng:

  • chuyển trang

  • tìm kiếm

  • sort

  • filter

DataTables sẽ gửi request tới server và server trả JSON về cho bảng. (DataTables Plus)


20. DataTables Client-side và Server-side

Có hai cách sử dụng.

Client-side

MySQL
 ↓
Laravel
 ↓
10.000 records
 ↓
Browser
 ↓
DataTables

Browser nhận toàn bộ:

10.000 records

sau đó DataTables xử lý.

Phù hợp với:

100
500
1.000

bản ghi tùy trường hợp.


Server-side

Browser
   ↓
DataTables
   ↓
Laravel
   ↓
MySQL

Browser chỉ nhận dữ liệu cần thiết cho trang hiện tại.

Ví dụ:

Database:

1.000.000 Users

Nhưng trang hiện tại chỉ cần:

10 Users

thì server xử lý:

Search
Sort
Filter
Pagination

rồi trả về 10 records.

Đây là cách phù hợp hơn với dữ liệu rất lớn. DataTables cũng mô tả server-side processing là cách để database thực hiện phần tìm kiếm, ordering và paging cho các tập dữ liệu lớn. (DataTables Plus)


21. Cài DataTables

Có thể sử dụng CDN.

Trong layout:

<link
    rel="stylesheet"
    href="https://cdn.datatables.net/2.3.4/css/dataTables.dataTables.min.css"
>

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<script src="https://cdn.datatables.net/2.3.4/js/dataTables.min.js"></script>

Lưu ý: Khi triển khai dự án thực tế, nên kiểm tra phiên bản DataTables hiện tại và cố định version phù hợp thay vì phụ thuộc vào một URL không version.


22. HTML Table

Blade:

<table id="users-table" class="display w-full">

    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
            <th>Created</th>
        </tr>
    </thead>

    <tbody>
    </tbody>

</table>

Chúng ta không cần:

@foreach

vì dữ liệu sẽ được DataTables lấy bằng AJAX.


23. Khởi tạo DataTables

JavaScript:

$(document).ready(function () {

    $('#users-table').DataTable({
        ajax: '{{ route('users.index') }}',

        columns: [
            {
                data: 'id'
            },
            {
                data: 'name'
            },
            {
                data: 'email'
            },
            {
                data: 'role'
            },
            {
                data: 'created_at'
            }
        ]
    });

});

DataTables sẽ gửi request:

/users

và mong server trả JSON.


24. Laravel trả JSON

Trong Controller:

public function index(Request $request)
{
    if ($request->ajax()) {

        $users = User::latest()->get();

        return response()->json([
            'data' => $users
        ]);
    }

    return view('users.index');
}

Khi AJAX request:

/users

Laravel trả:

{
    "data": [
        {
            "id": 1,
            "name": "Nguyễn Văn A",
            "email": "a@gmail.com",
            "role": "admin"
        },
        {
            "id": 2,
            "name": "Trần Văn B",
            "email": "b@gmail.com",
            "role": "user"
        }
    ]
}

DataTables đọc:

data: 'id'
data: 'name'
data: 'email'

và đưa dữ liệu vào bảng.


25. Nhưng có một vấn đề

Đoạn này:

User::latest()->get();

sẽ lấy toàn bộ User.

Nếu database có:

500.000 Users

thì:

500.000 Users
       ↓
Laravel
       ↓
JSON
       ↓
Browser

là điều không nên làm.

Đây chính là lúc chúng ta cần:

Server-side DataTables


26. Server-side DataTables

JavaScript:

$('#users-table').DataTable({

    processing: true,

    serverSide: true,

    ajax: '{{ route('users.index') }}',

    columns: [
        {
            data: 'id'
        },
        {
            data: 'name'
        },
        {
            data: 'email'
        },
        {
            data: 'role'
        },
        {
            data: 'created_at'
        }
    ]

});

Hai dòng quan trọng:

processing: true,
serverSide: true,

DataTables sẽ gửi các thông tin cần thiết cho server để server thực hiện processing. (DataTables Plus)


27. DataTables gửi gì lên Laravel?

Ví dụ người dùng đang ở:

Trang 3

DataTables có thể gửi các thông tin liên quan đến:

start
length
search
order
columns
draw

Ví dụ ý tưởng:

start = 20
length = 10
search = nguyen

Có nghĩa:

Bắt đầu từ record 20
Lấy 10 record
Search "nguyen"

Server xử lý:

MySQL
 ↓
WHERE
ORDER BY
LIMIT
OFFSET

rồi trả kết quả.


28. Laravel xử lý Server-side

Ví dụ đơn giản:

public function index(Request $request)
{
    if ($request->ajax()) {

        $query = User::query();

        /*
        |--------------------------------------------------------------------------
        | SEARCH
        |--------------------------------------------------------------------------
        */

        if ($request->search['value']) {

            $search = $request->search['value'];

            $query->where(function ($q) use ($search) {

                $q->where('name', 'like', "%{$search}%")
                  ->orWhere('email', 'like', "%{$search}%");

            });
        }


        /*
        |--------------------------------------------------------------------------
        | TOTAL
        |--------------------------------------------------------------------------
        */

        $recordsTotal = User::count();

        $recordsFiltered = $query->count();


        /*
        |--------------------------------------------------------------------------
        | ORDER
        |--------------------------------------------------------------------------
        */

        $query->orderBy('id', 'desc');


        /*
        |--------------------------------------------------------------------------
        | PAGINATION
        |--------------------------------------------------------------------------
        */

        $users = $query
            ->skip($request->start)
            ->take($request->length)
            ->get();


        /*
        |--------------------------------------------------------------------------
        | RESPONSE
        |--------------------------------------------------------------------------
        */

        return response()->json([

            'draw' => intval($request->draw),

            'recordsTotal' => $recordsTotal,

            'recordsFiltered' => $recordsFiltered,

            'data' => $users,

        ]);
    }

    return view('users.index');
}

Đây là phiên bản tự xử lý protocol của DataTables, rất tốt để người học hiểu bản chất.


29. Cấu trúc Response

DataTables cần các thông tin chính:

{
    "draw": 1,
    "recordsTotal": 500,
    "recordsFiltered": 25,
    "data": []
}

Ý nghĩa:

draw

Đánh dấu request hiện tại.

recordsTotal

Tổng số User trước khi filter:

500

recordsFiltered

Tổng số User sau filter:

25

data

Dữ liệu của trang hiện tại:

10 Users

30. Sort từ DataTables

Bây giờ người dùng click:

Name ↕

DataTables gửi thông tin column/order.

Ví dụ:

order[0][column] = 1
order[0][dir] = asc

Ý nghĩa:

Column 1
↓
ASC

Chúng ta có thể xử lý:

$columns = [
    'id',
    'name',
    'email',
    'role',
    'created_at',
];

$orderColumn = $columns[$request->order[0]['column']] ?? 'id';

$orderDirection = $request->order[0]['dir'] ?? 'desc';

$query->orderBy(
    $orderColumn,
    $orderDirection
);

31. Không nên cho phép Sort tùy ý

Không nên viết:

$query->orderBy(
    $request->order[0]['column'],
    $request->order[0]['dir']
);

vì giá trị từ browser là input không đáng tin cậy.

Thay vào đó:

$columns = [
    'id',
    'name',
    'email',
    'role',
    'created_at',
];

sau đó map:

$orderColumn = $columns[$index] ?? 'id';

Đây là một nguyên tắc rất quan trọng:

Không đưa trực tiếp tên column từ request của người dùng vào câu SQL nếu không kiểm soát whitelist.


32. Search + Sort + Pagination

Đến đây DataTables đã có:

                 ┌──────────────┐
                 │  DataTables  │
                 └──────┬───────┘
                        │
              ┌─────────┼─────────┐
              ↓         ↓         ↓
           Search      Sort     Paging
              │         │         │
              └─────────┼─────────┘
                        ↓
                    Laravel
                        ↓
                     MySQL

Đây là kiến trúc rất phổ biến trong Admin Dashboard.


33. Khi nào dùng Laravel Pagination?

Dùng:

paginate()

khi:

  • Website đơn giản

  • Blog

  • Danh sách bài viết

  • Danh mục

  • Trang frontend

  • Không cần Ajax

  • Muốn SEO tốt

  • Muốn code đơn giản

Ví dụ:

$posts = Post::latest()->paginate(12);

34. Khi nào dùng DataTables?

DataTables rất phù hợp với:

Admin Dashboard

Ví dụ:

Users
Posts
Orders
Products
Invoices
Customers
Transactions
Logs

Đặc biệt khi cần:

Search
Sort
Filter
Pagination
Ajax

trong cùng một bảng.


35. Khi nào dùng Server-side DataTables?

Khi dữ liệu lớn.

Ví dụ:

1.000 records

Có thể client-side vẫn ổn tùy trường hợp.

Nhưng:

100.000
500.000
1.000.000+

thì server-side processing thường hợp lý hơn.

DataTables cũng khuyến nghị server-side processing cho các tập dữ liệu rất lớn, vì database có thể thực hiện các tác vụ filtering, ordering và paging hiệu quả hơn phía browser. (DataTables Plus)


36. Một lỗi rất hay gặp

Không nên viết:

$users = User::all();

rồi:

collect($users)
    ->filter(...)
    ->sort(...)
    ->paginate(...);

Đối với dữ liệu lớn, đây là cách không tốt.

Vì:

MySQL
 ↓
Tất cả records
 ↓
PHP RAM
 ↓
Filter
 ↓
Sort
 ↓
Pagination

Thay vào đó:

MySQL
 ↓
WHERE
ORDER BY
LIMIT
 ↓
Laravel
 ↓
Browser

Database phải làm phần lớn công việc.


37. Tư duy Query Builder quan trọng

Thay vì:

$users = User::all();

hãy hình thành thói quen:

$query = User::query();

Sau đó:

if (...) {
    $query->where(...);
}

if (...) {
    $query->orderBy(...);
}

$users = $query->paginate(10);

Đây là tư duy rất quan trọng khi xây dựng Admin CMS.


38. Tách Filter thành Method

Khi Controller bắt đầu dài, chúng ta có thể đưa logic filter vào Model.

Ví dụ:

class User extends Authenticatable
{
    public function scopeSearch($query, $search)
    {
        return $query->where(function ($q) use ($search) {

            $q->where('name', 'like', "%{$search}%")
              ->orWhere('email', 'like', "%{$search}%");

        });
    }
}

Controller:

$query = User::query();

if ($search = request('search')) {
    $query->search($search);
}

Code Controller trở nên sạch hơn.

Phần Scope sẽ được học sâu hơn ở:

📘 Bài 32 — Local Scope & Global Scope


39. Tách Filter thành Service

Trong dự án lớn hơn:

Controller
    ↓
Service
    ↓
Repository / Query
    ↓
Model
    ↓
Database

Ví dụ:

UserController
      ↓
UserService
      ↓
User Query
      ↓
MySQL

Nhưng ở giai đoạn hiện tại không nên làm quá phức tạp.

Người mới học Laravel chỉ cần nắm chắc:

User::query()

where()

orderBy()

paginate()

là đã rất quan trọng.


40. Validation cho Search

Search cũng là dữ liệu từ người dùng.

Laravel 13 cung cấp nhiều cách validation request, từ $request->validate() đến Form Request cho các trường hợp phức tạp hơn. (Laravel)

Ví dụ:

$request->validate([
    'search' => ['nullable', 'string', 'max:100'],
    'role' => ['nullable', 'in:admin,editor,user'],
]);

Không nhất thiết mọi ô search đều phải validation phức tạp, nhưng đây là thói quen tốt.


41. Một Controller thực tế hơn

Ví dụ:

public function index(Request $request)
{
    $request->validate([
        'search' => ['nullable', 'string', 'max:100'],
        'role' => ['nullable', 'in:admin,editor,user'],
        'sort' => [
            'nullable',
            'in:latest,oldest,name_asc,name_desc'
        ],
    ]);


    $query = User::query();


    // SEARCH
    if ($search = $request->search) {

        $query->where(function ($q) use ($search) {

            $q->where('name', 'like', "%{$search}%")
              ->orWhere('email', 'like', "%{$search}%");

        });
    }


    // FILTER
    if ($request->role) {
        $query->where('role', $request->role);
    }


    // SORT
    match ($request->sort) {

        'oldest' => $query->oldest(),

        'name_asc' => $query->orderBy('name'),

        'name_desc' => $query->orderByDesc('name'),

        default => $query->latest(),

    };


    // PAGINATION
    $users = $query
        ->paginate(10)
        ->withQueryString();


    return view('users.index', compact('users'));
}

Đây là phiên bản khá tốt cho một CRUD Admin cơ bản.


42. Tổng kết kiến trúc

Sau bài này, chúng ta đã đi từ:

User::all();

đến:

User::query()

và xây dựng:

                USERS
                  │
                  ↓
             User::query()
                  │
       ┌──────────┼──────────┐
       ↓          ↓          ↓
    Search      Filter      Sort
       │          │          │
       └──────────┼──────────┘
                  ↓
             Pagination
                  │
                  ↓
                Blade

Hoặc với DataTables:

              Browser
                 │
                 ↓
            DataTables
                 │
                AJAX
                 ↓
              Laravel
                 │
                 ↓
             Eloquent
                 │
                 ↓
              MySQL
                 │
                 ↓
               JSON
                 │
                 ↓
            DataTables

43. Bài tập thực hành

Bài tập 1

Tạo trang:

/users

có:

Search
Pagination

Bài tập 2

Thêm:

Filter Role

với:

All
Admin
Editor
User

Bài tập 3

Thêm:

Sort

gồm:

Mới nhất
Cũ nhất
Tên A → Z
Tên Z → A

Bài tập 4

Kết hợp:

Search
+
Filter
+
Sort
+
Pagination

và đảm bảo khi chuyển trang các query vẫn được giữ lại.


Bài tập 5

Cài:

DataTables

và hiển thị User bằng AJAX.


Bài tập 6 — Nâng cao

Chuyển DataTables sang:

serverSide: true

và để Laravel xử lý:

Search
Sort
Pagination

thay vì tải toàn bộ User về browser.


🎯 Kết quả sau Bài 29

Người học đã có thể xây dựng một bảng Admin hoàn chỉnh:

┌─────────────────────────────────────────────────────────────┐
│ 👥 USERS                                  [+ Thêm User]     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│ 🔎 [ Tìm tên/email ]                                        │
│                                                             │
│ Role: [ Tất cả ▼ ]     Sort: [ Mới nhất ▼ ]                │
│                                                             │
├────┬────────────────┬──────────────────┬─────────┬─────────┤
│ ID │ Họ tên         │ Email            │ Role    │ Action  │
├────┼────────────────┼──────────────────┼─────────┼─────────┤
│ 1  │ Nguyễn Văn A   │ a@gmail.com      │ Admin   │ ✏️ 🗑️  │
│ 2  │ Trần Văn B     │ b@gmail.com      │ User    │ ✏️ 🗑️  │
│ 3  │ Lê Văn C       │ c@gmail.com      │ Editor  │ ✏️ 🗑️  │
└────┴────────────────┴──────────────────┴─────────┴─────────┘

              1   2   3   4   5   Next →

Đây là bước chuyển rất quan trọng từ CRUD cơ bản sang Admin Dashboard thực tế.

Ghi nhớ công thức:

$query = Model::query();

$query->where(...);

$query->orderBy(...);

$query->paginate(...);

Và khi dữ liệu lớn:

DataTables
     +
AJAX
     +
serverSide
     +
Laravel
     +
MySQL

sẽ trở thành một kiến trúc rất hữu ích cho các bảng quản trị lớn. (DataTables Plus)

Bài tiếp theo — Bài 30: Soft Delete sẽ xây dựng hệ thống Thùng rác (Trash) với SoftDeletes, restore()forceDelete(), sau đó áp dụng trực tiếp vào CRUD Users/Posts.

Không có nhận xét nào:

Đăng nhận xét

Facebook Youtube RSS