Przejdź do treści

Paginacja (stronicowanie) wyników z bazy danych

  • Baza danych
  • poziom: średni
  • aktualizacja: 12 lipca 2026

Gdy rekordów są setki, nie wyświetla się ich wszystkich naraz — dzieli się je na strony. Ten skrypt pokazuje kompletną paginację: liczy rekordy, pilnuje zakresu numeru strony, pobiera właściwy wycinek danych i rysuje eleganckie linki z wielokropkami.

PHP 115 linii Pobierz .php
<?php
/**
 * Paginacja wyników z bazy — skryptyPHP.com
 * Zapisz jako: artykuly.php
 *
 * Przykładowa tabela:
 *   CREATE TABLE artykuly (
 *       id     INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 *       tytul  VARCHAR(200) NOT NULL,
 *       dodano DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
 *   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 */

require 'baza.php'; // skrypt „Połączenie z bazą (PDO)"
$pdo = polacz_z_baza();

// ── USTAWIENIA ──────────────────────────────────────────────
$na_strone = 10; // ile rekordów na jednej stronie

// ── OBLICZENIA ──────────────────────────────────────────────
$ile_rekordow = (int) $pdo->query('SELECT COUNT(*) FROM artykuly')->fetchColumn();
$ile_stron    = max(1, (int) ceil($ile_rekordow / $na_strone));

// Numer strony z adresu — przycięty do dozwolonego zakresu
$strona = (int) ($_GET['strona'] ?? 1);
$strona = max(1, min($strona, $ile_stron));

$offset = ($strona - 1) * $na_strone;

// LIMIT i OFFSET podajemy jako parametry typu INT (nie sklejamy z SQL-em)
$stmt = $pdo->prepare(
    'SELECT id, tytul, dodano
       FROM artykuly
      ORDER BY dodano DESC
      LIMIT :limit OFFSET :offset'
);
$stmt->bindValue(':limit',  $na_strone, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset,    PDO::PARAM_INT);
$stmt->execute();
$artykuly = $stmt->fetchAll();

/**
 * Zwraca listę numerów stron do wyświetlenia, np. [1, '…', 4, 5, 6, '…', 20].
 * $otoczenie — ile numerów pokazać z każdej strony bieżącej.
 */
function numery_paginacji(int $biezaca, int $ostatnia, int $otoczenie = 2): array
{
    $numery = [];
    $poprzedni = 0;

    for ($i = 1; $i <= $ostatnia; $i++) {
        $pokaz = $i === 1                                  // pierwsza
              || $i === $ostatnia                          // ostatnia
              || abs($i - $biezaca) <= $otoczenie;         // okolice bieżącej

        if ($pokaz) {
            if ($poprzedni !== 0 && $i - $poprzedni > 1) {
                $numery[] = '…'; // przerwa w numeracji
            }
            $numery[] = $i;
            $poprzedni = $i;
        }
    }
    return $numery;
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Artykuły — strona <?= $strona ?></title>
<style>
    body { font-family: sans-serif; max-width: 640px; margin: 20px auto; }
    .paginacja { display: flex; gap: 6px; margin-top: 20px; flex-wrap: wrap; }
    .paginacja a, .paginacja span {
        padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px;
        text-decoration: none; color: #333;
    }
    .paginacja .biezaca { background: #333; color: #fff; border-color: #333; }
    .paginacja .kropki  { border: none; }
</style>
</head>
<body>

<h1>Artykuły (<?= $ile_rekordow ?>)</h1>

<ul>
    <?php foreach ($artykuly as $a): ?>
        <li><?= htmlspecialchars($a['tytul']) ?>
            <small>(<?= htmlspecialchars($a['dodano']) ?>)</small></li>
    <?php endforeach; ?>
</ul>

<nav class="paginacja" aria-label="Wybór strony">
    <?php if ($strona > 1): ?>
        <a href="?strona=<?= $strona - 1 ?>">&laquo; poprzednia</a>
    <?php endif; ?>

    <?php foreach (numery_paginacji($strona, $ile_stron) as $n): ?>
        <?php if ($n === '…'): ?>
            <span class="kropki">…</span>
        <?php elseif ($n === $strona): ?>
            <span class="biezaca"><?= $n ?></span>
        <?php else: ?>
            <a href="?strona=<?= $n ?>"><?= $n ?></a>
        <?php endif; ?>
    <?php endforeach; ?>

    <?php if ($strona < $ile_stron): ?>
        <a href="?strona=<?= $strona + 1 ?>">następna &raquo;</a>
    <?php endif; ?>
</nav>

</body>
</html>

Jak użyć tego skryptu

  1. Podłącz plik baza.php (skrypt „Bezpieczne połączenie z bazą MySQL") i zmień nazwę tabeli oraz kolumny na swoje.
  2. Ustaw liczbę rekordów na stronę w zmiennej $na_strone.
  3. Numer strony przekazywany jest w adresie jako ?strona=3 — funkcja przycinająca zakres sama obsłuży wartości spoza zakresu.

Warto wiedzieć

  • Wartości LIMIT i OFFSET wiążemy przez bindValue(..., PDO::PARAM_INT) — MySQL nie pozwala przekazać ich jako tekstu, a sklejanie liczby z zapytaniem to prosta droga do błędów.
  • Numer strony zawsze przycinamy funkcjami max()/min() — użytkownik może wpisać w adres cokolwiek.
  • Przy bardzo dużych tabelach (setki tysięcy wierszy) OFFSET robi się wolny — wtedy stosuje się tzw. keyset pagination (WHERE id < ostatnie_id), ale dla typowych stron ten skrypt w zupełności wystarczy.
  • Jeśli strony z paginacją mają trafiać do Google, dodaj w <head> znacznik rel="canonical" wskazujący pierwszą stronę listy.
#paginacja #stronicowanie #pdo #limit #offset

Podobne skrypty