В SQL-запросах, для ограничения количества строк в результате используется инструкция LIMIT
, например следующий вернёт первые 10 записей:
В следующем запросе будут записи с 11 по 20:
Для построения пагинации требуется знать сколько всего записей вернет SQL-запрос без LIMIT, для этого в MySQL есть опция SQL_CALC_FOUND_ROWS
. С ней запрос вернет обычный результат.
Но теперь, если следом выполнить запрос SELECT FOUND_ROWS()
, то он вернёт общее количество записей в предыдущем запросе.
На этом принципе был написан PHP-класс для постраничного вывода из базы данных.
class DBPaginator
{
public $page = 1; /* Текущая страница */
public $amt = 0; /* Кол-во страниц */
public $limit = 10; /* Кол-во элементов на странице */
public $total = 0; /* Общее кол-во элементов */
public $display = ''; /* HTML-код навигации */
private $url = '';
private $carrier = 'page';
/**
* Конструктор.
*/
public function __construct($url, $limit = 0)
{
$this->url = $url;
if (!empty($limit)) {
$this->limit = $limit;
}
$page = intval(@$_GET['page']);
if (!empty($page)) {
$this->page = $page;
}
$query = parse_url($this->url, PHP_URL_QUERY);
if (empty($query)) {
$this->carrier = '?' . $this->carrier . '=';
} else {
$this->carrier = '&' . $this->carrier . '=';
}
}
/**
* Формирование HTML-кода навигации в переменную display.
*/
public function getItems($sql)
{
// Подключение к БД
$dbh = new PDO('mysql:dbname=db_name;host=localhost', 'логин', 'пароль');
// Получение записей для текущей страницы
$start = ($this->page != 1) ? $this->page * $this->limit - $this->limit : 0;
if (strstr($sql, 'SQL_CALC_FOUND_ROWS') === false) {
$sql = str_replace('SELECT ', 'SELECT SQL_CALC_FOUND_ROWS ', $sql) . ' LIMIT ' . $start . ', ' . $this->limit;
} else {
$sql = $sql . ' LIMIT ' . $start . ', ' . $this->limit;
}
$sth = $dbh->prepare($sql);
$sth->execute();
$array = $sth->fetchAll(PDO::FETCH_ASSOC);
// Узнаем сколько всего записей в БД
$sth = $dbh->prepare("SELECT FOUND_ROWS()");
$sth->execute();
$this->total = $sth->fetch(PDO::FETCH_COLUMN);
$this->amt = ceil($this->total / $this->limit);
if ($this->page > $this->amt) {
$this->page = $this->amt;
}
if ($this->amt > 1) {
$adj = 2;
$this->display = '<nav class="pagination-row"><ul class="pagination justify-content-center">';
/* Назад */
if ($this->page == 1) {
$this->addSpan('«', 'prev disabled');
} elseif ($this->page == 2) {
$this->addLink('«', '', 'prev');
} else {
$this->addLink('«', $this->carrier . ($this->page - 1), 'prev');
}
if ($this->amt < 7 + ($adj * 2)) {
for ($i = 1; $i <= $this->amt; $i++){
$this->addLink($i, $this->carrier . $i);
}
} elseif ($this->amt > 5 + ($adj * 2)) {
$lpm = $this->amt - 1;
if ($this->page < 1 + ($adj * 2)){
for ($i = 1; $i < 4 + ($adj * 2); $i++){
$this->addLink($i, $this->carrier . $i);
}
$this->addSpan('...', 'separator');
$this->addLink($lpm, $this->carrier . $lpm);
$this->addLink($this->amt, $this->carrier . $this->amt);
} elseif ($this->amt - ($adj * 2) > $this->page && $this->page > ($adj * 2)) {
$this->addLink(1);
$this->addLink(2, $this->carrier . '2');
$this->addSpan('...', 'separator');
for ($i = $this->page - $adj; $i <= $this->page + $adj; $i++) {
$this->addLink($i, $this->carrier . $i);
}
$this->addSpan('...', 'separator');
$this->addLink($lpm, $this->carrier . $lpm);
$this->addLink($this->amt, $this->carrier . $this->amt);
} else {
$this->addLink(1, '');
$this->addLink(2, $this->carrier . '2');
$this->addSpan('...', 'separator');
for ($i = $this->amt - (2 + ($adj * 2)); $i <= $this->amt; $i++) {
$this->addLink($i, $this->carrier . $i);
}
}
}
/* Далее */
if ($this->page == $this->amt) {
$this->addSpan('»', 'next disabled');
} else {
$this->addLink('»', $this->carrier . ($this->page + 1));
}
$this->display .= '</ul></nav>';
}
return $array;
}
private function addSpan($text, $class = '')
{
$class = 'page-item ' . $class;
$this->display .= '<li class="' . trim($class) . '"><span class="page-link">' . $text . '</span></li>';
}
private function addLink($text, $url = '', $class = '')
{
if ($text == 1) {
$url = '';
}
$class = 'page-item ' . $class . ' ';
if ($text == $this->page) {
$class .= 'active';
}
$this->display .= '<li class="' . trim($class) . '"><a class="page-link" href="' . $this->url . $url . '">' . $text . '</a></li>';
}
}
Использование класса
В класс нужно передать URL страницы, количество записей на странице и SQL-запрос выборки из БД (например товары).
<?php
// Текущий URL и 6 шт. на странице
$peger = new DBPaginator('https://example.com', 6);
$items = $peger->getItems("SELECT * FROM `prods`");
/* Инфо о текущей странице */
echo '<p>Страница ' . $peger->page . ' из ' . $peger->amt . '</p>';
/* Вывод текста только на первой странице */
if ($peger->page == 1) {
echo '<p>Описание на первой странице</p>';
}
?>
<div class="prod-list">
<?php foreach ($items as $row): ?>
<div class="prod-item">
<div class="prod-item-img">
<a href="#"><img src="/images/<?php echo $row['img']; ?>" alt=""></a>
</div>
<div class="prod-item-name">
<a href="#"><?php echo $row['name']; ?></a>
</div>
<div class="prod-item-btn">
<a href="#">Купить</a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php echo $peger->display; ?>
CSS-стили пагинатора:
.pagination-row {
overflow: hidden;
clear: both;
margin: 0 0 20px 0;
margin-bottom: 15px;
}
.pagination {
padding: 0;
margin: 0;
text-align: center;
}
.pagination .page-item {
display: inline-block;
margin: 0 2px 3px;
}
.pagination .page-link {
display: inline-block;
height: 28px;
min-width: 28px;
line-height: 28px;
font-size: 15px;
text-decoration: none;
text-align: center;
border: 1px solid #ddd;
border-radius: 3px;
background: #fbfbfb;
text-decoration: none;
color: #000;
}
.pagination a.page-link:hover {
background: #ffd57b;
}
.pagination .active a.page-link {
background: #2bc2e0;
border-color: #a5a5ca;
}
.pagination .separator .page-link {
border-color: #fff;
background: #fff;
}
.pagination .disabled .page-link {
color: #999;
}
Стили для списка товаров:
.prod-list {
overflow: hidden;
margin: 0 -25px 20px 0;
}
.prod-item {
width: 180px;
height: 290px;
float: left;
border: 1px solid #ddd;
padding: 20px;
margin: 0 20px 20px 0;
text-align: center;
border-radius: 6px;
}
.prod-item-name {
margin-bottom: 5px;
}
.prod-item-btn a {
color: rgb(25, 25, 25);
line-height: 24px;
border-radius: 4px;
background-color: #2bc2e0;
display: inline-block;
text-decoration: none;
padding: 0 30px;
border: none;
box-shadow: 0px 2px 1px 0px #a5a5ca;
}