เราต้องเคยเจอเคส การแสดงผลข้อมูลจำนวนมากในหน้าเว็บเดียว ทำให้การโหลดช้าลงและทำให้ผู้ใช้งานรู้สึกไม่สะดวก เราจึงต้องใช้ pagination (การแบ่งหน้า) เป็นหนึ่งในวิธีที่ช่วยจัดการข้อมูลจำนวนมากโดยการแสดงผลในแต่ละหน้าแทนที่จะโหลดข้อมูลทั้งหมดในหน้าเดียว
เราใช้ Bootstrap เพื่อช่วยในการตกแต่ง UI นะครับ
ในบทความนี้เราจะมาดูวิธีการสร้างคลาส Pagination ใน PHP และการใช้งาน ซึ่งประกอบไปด้วยการจัดการข้อมูลในฐานข้อมูล การคำนวณจำนวนหน้าทั้งหมด และการแสดงผลลัพธ์ที่แบ่งหน้าได้อย่างสะดวก
โครงสร้างของคลาส Pagination
คลาส Pagination ในตัวอย่างนี้ประกอบด้วยหลายฟังก์ชันที่ช่วยในการจัดการการแบ่งหน้าและการเรียกดูข้อมูลจากฐานข้อมูล
class Pagination
{
private $db;
private $query;
private $rowsPerPage;
private $currentPage;
private $totalRows;
private $totalPages;
private $searchValue;
private $startDateValue;
private $endDateValue;
private $dateColumn;
private $orderByColumn;
private $groupByColumn;
public function __construct($db, $query, $rowsPerPage = 10, $currentPage = 1, $dateColumn = '')
{
$this->db = $db;
$this->query = $query;
$this->rowsPerPage = max(1, (int)$rowsPerPage);
$this->currentPage = max(1, (int)$currentPage);
$this->dateColumn = $dateColumn;
$this->getSearchValueFromRequest();
$this->calculateTotalRows();
$this->calculateTotalPages();
}
// ฟังก์ชันที่ใช้ในการดึงข้อมูลการค้นหาจากผู้ใช้
public function getSearchValueFromRequest()
{
if ($this->dateColumn) {
$this->searchValue = isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '';
$this->startDateValue = isset($_GET['startDate']) ? htmlspecialchars($_GET['startDate']) : date('Y-m-01');
$this->endDateValue = isset($_GET['endDate']) ? htmlspecialchars($_GET['endDate']) : date('Y-m-t');
if (!empty($this->startDateValue) && !empty($this->endDateValue)) {
$this->query .= " AND {$this->dateColumn} BETWEEN '$this->startDateValue' AND '$this->endDateValue'";
} elseif (!empty($this->startDateValue)) {
$this->query .= " AND {$this->dateColumn} >= '$this->startDateValue'";
} elseif (!empty($this->endDateValue)) {
$this->query .= " AND {$this->dateColumn} <= '$this->endDateValue'";
}
}
}
// ฟังก์ชันคำนวณจำนวนแถวทั้งหมด
private function calculateTotalRows()
{
$countQuery = "SELECT COUNT(*) AS total FROM ($this->query) AS subquery";
try {
$stmt = $this->db->prepare($countQuery);
if ($stmt) {
$stmt->bind_param();
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$this->totalRows = $row['total'];
}
} catch (Exception $e) {
error_log("Error calculating total rows: " . $e->getMessage());
$this->totalRows = 0;
}
}
// ฟังก์ชันคำนวณจำนวนหน้าทั้งหมด
private function calculateTotalPages()
{
$this->totalPages = ceil($this->totalRows / $this->rowsPerPage);
}
// ฟังก์ชันดึงผลลัพธ์ข้อมูลตามหน้าปัจจุบัน
public function getResults()
{
$offset = ($this->currentPage - 1) * $this->rowsPerPage;
$paginatedQuery = $this->query;
if (!empty($this->groupByColumn)) {
$paginatedQuery .= " GROUP BY {$this->groupByColumn}";
}
if (!empty($this->orderByColumn)) {
$paginatedQuery .= " ORDER BY {$this->orderByColumn}";
}
$paginatedQuery .= " LIMIT ?, ?";
try {
$stmt = $this->db->prepare($paginatedQuery);
$params = [];
$params[] = $offset;
$params[] = $this->rowsPerPage;
if ($stmt) {
$stmt->bind_param('ii', ...$params);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
return [
'data' => $data,
'totalRows' => $this->totalRows,
'totalPages' => $this->totalPages,
'currentPage' => $this->currentPage,
];
}
} catch (Exception $e) {
error_log("Error fetching paginated results: " . $e->getMessage());
return [
'data' => [],
'totalRows' => $this->totalRows,
'totalPages' => $this->totalPages,
'currentPage' => $this->currentPage,
];
}
}
public function renderLinks($baseUrl, $paramName = 'page')
{
if ($this->totalPages <= 1) {
return '';
}
$links = '<nav class="overflow-scroll" > <ul class="pagination">';
if ($this->currentPage > 1) {
$links .= '<li class="page-item"><a class="page-link" href="' . $this->buildUrl($baseUrl, $paramName, $this->currentPage - 1) . '">ก่อนหน้า</a></li>';
}
$maxVisiblePages = 5;
$startPage = max(1, $this->currentPage - floor($maxVisiblePages / 2));
$endPage = min($this->totalPages, $startPage + $maxVisiblePages - 1);
if ($startPage > 1) {
$links .= '<li class="page-item"><a class="page-link" href="' . $this->buildUrl($baseUrl, $paramName, 1) . '">1</a></li>';
if ($startPage > 2) {
$links .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
}
for ($i = $startPage; $i <= $endPage; $i++) {
$active = $this->currentPage == $i ? 'active' : '';
$links .= '<li class="page-item ' . $active . '"><a class="page-link" href="' . $this->buildUrl($baseUrl, $paramName, $i) . '">' . $i . '</a></li>';
}
if ($endPage < $this->totalPages) {
if ($endPage < $this->totalPages - 1) {
$links .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
$links .= '<li class="page-item"><a class="page-link" href="' . $this->buildUrl($baseUrl, $paramName, $this->totalPages) . '">' . $this->totalPages . '</a></li>';
}
if ($this->currentPage < $this->totalPages) {
$links .= '<li class="page-item"><a class="page-link" href="' . $this->buildUrl($baseUrl, $paramName, $this->currentPage + 1) . '">ถัดไป</a></li>';
}
$links .= '</ul></nav>';
return $links;
}
private function buildUrl($baseUrl, $paramName, $page)
{
$url = rtrim($baseUrl, '/');
$url .= (strpos($baseUrl, '?') === false ? '?' : '&') . "{$paramName}={$page}";
return $url;
}
public function renderRowsPerPageSelector($baseUrl)
{
$options = [10, 20, 50, 100, 1000];
$selector = '<select onchange="window.location.href=this.value" class="form-control">';
foreach ($options as $option) {
$selected = $this->rowsPerPage == $option ? 'selected' : '';
$url = $this->buildUrl($baseUrl, 'rowsPerPage', $option);
$selector .= "<option value='{$url}' {$selected}>{$option} แถว</option>";
}
$selector .= '</select>';
return $selector;
}
function generateRowNumber($currentPage, $rowsPerPage)
{
return ($currentPage - 1) * $rowsPerPage + 1;
}
public function renderSearchForm($tab = "")
{
return "
<div class='search my-3'>
<form method='GET' action=''>
<input type='text' hidden class='form-control' id='endDate' name='tab' value='$tab'>
<div class='row'>
<div class='col-sm-3 my-1'>
<label for='search' class='form-label'>ค้นหาข้อมูล</label>
<input type='text' class='form-control' id='search' name='search' placeholder='ค้นหา' value='$this->searchValue'>
</div>
<div class='col-sm-3 my-1'>
<label for='startDate' class='form-label'>วันที่เริ่มต้น</label>
<input type='date' class='form-control' id='startDate' name='startDate' value='$this->startDateValue'>
</div>
<div class='col-sm-3 my-1'>
<label for='endDate' class='form-label'>วันที่สิ้นสุด</label>
<input type='date' class='form-control' id='endDate' name='endDate' value='$this->endDateValue'>
</div>
<div class='col-sm-3 my-1'>
<label for='filterBtn' class='form-label'> </label><br>
<button id='filterBtn' class='btn btn-primary form-control' type='submit'>ค้นหา</button>
</div>
</div>
</form>
</div>";
}
public function renderNoDataRow($colspan = 1, $message = 'ไม่พบข้อมูล')
{
return "<tr>
<td colspan='{$colspan}' class='text-center'>{$message}</td>
</tr>";
}
}
ในตัวอย่างนี้ เราได้สร้างฟังก์ชันที่ช่วยให้สามารถกำหนดจำนวนแถวต่อหน้า (rows per page), เลือกการกรองข้อมูลด้วยการค้นหาและช่วงเวลา, และการแสดงลิงก์ของแต่ละหน้าได้ง่ายๆ ซึ่งตัวแปรที่ใช้ในการเชื่อมต่อกับฐานข้อมูลจะมีการกำหนดที่หน้าเพจหลักตามความต้องการของผู้ใช้
การใช้งานฟังก์ชัน Pagination
การใช้งานคลาสนี้จะช่วยให้เว็บมีการแสดงผลข้อมูลที่เป็นระเบียบและแบ่งหน้าได้
<?php
$baseQuery = "SELECT * FROM user";
$searchValue = isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '';
if (!empty($searchValue)) {
$baseQuery .= " AND (user.user_name LIKE '%$searchValue%' OR user.firstname LIKE '%$searchValue%' OR user.lastname LIKE '%$searchValue%')";
}
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int)$_GET['page'] : 1;
$rowsPerPage = isset($_GET['rowsPerPage']) && is_numeric($_GET['rowsPerPage']) ? (int)$_GET['rowsPerPage'] : 10;
$pagination = new Pagination($db, $baseQuery, $rowsPerPage, $page, 'user.id');
// $pagination->setGroupByColumn('user.id');
$pagination->setOrderByColumn('user.id');
$results = $pagination->getResults();
$data = $results['data'];
$totalPages = $results['totalPages'];
$currentPage = $results['currentPage'];
?>
// แสดงผล
<?php echo $pagination->renderSearchForm('3'); ?>
<?php echo $pagination->getTotal(); ?>
<?php
if (empty($data)) {
echo $pagination->renderNoDataRow(9);
}
$i_numrow_table_ = $pagination->generateRowNumber($currentPage, $rowsPerPage);
foreach ($data as $row) {
?>
<?php echo $i_numrow_table_; $i_numrow_table_++ ?>
// Other...
<?php } ?>
<?php
<div class="row my-3">
<div class="col-10 d-flex justify-content-start">
<?php echo $pagination->renderLinks('?tab=20'); ?>
</div>
<div class="col-2 d-flex justify-content-end">
<?php echo $pagination->renderRowsPerPageSelector('?tab=20'); ?>
</div>
</div>
?>
สรุป
บทความนี้ได้แสดงตัวอย่างการสร้างคลาส Pagination เพื่อให้สามารถแบ่งหน้าและแสดงผลข้อมูลในระบบฐานข้อมูล การใช้ Pagination จะช่วยให้ระบบมีประสิทธิภาพในการจัดการข้อมูลจำนวนมากได้ดียิ่งขึ้น สามารถนำไปปรับแต่งใช้งานต่อได้เลยครับ