สร้าง Pagination Class และ Filter ใน PHP

Nov. 27, 2024 · boychawin

เราต้องเคยเจอเคส การแสดงผลข้อมูลจำนวนมากในหน้าเว็บเดียว ทำให้การโหลดช้าลงและทำให้ผู้ใช้งานรู้สึกไม่สะดวก เราจึงต้องใช้ 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'>&nbsp;</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 จะช่วยให้ระบบมีประสิทธิภาพในการจัดการข้อมูลจำนวนมากได้ดียิ่งขึ้น สามารถนำไปปรับแต่งใช้งานต่อได้เลยครับ

Tags: javascript , php