Introduzione
Quando un database contiene molte righe, mostrare tutti i risultati in una sola volta non è una buona idea. La pagina diventa lenta, l’utente fatica a orientarsi e il server lavora più del necessario. In questi casi, una tecnica fondamentale è la paginazione: invece di caricare tutto, si recupera solo un “blocco” di record alla volta.
In PHP, la paginazione con MySQL/MariaDB è un argomento molto utile nella pratica quotidiana: elenchi di prodotti, articoli, utenti, ordini, log, commenti. L’idea è semplice: si usa LIMIT per definire quanti record ottenere e OFFSET per saltare quelli delle pagine precedenti.
In questo tutorial vedremo come implementare una paginazione solida con PDO, con attenzione alla sicurezza, alla chiarezza del codice e a piccoli accorgimenti che migliorano davvero l’esperienza utente.
Codice completo
<?php
declare(strict_types=1);
// Configurazione connessione
$dsn = ´mysql:host=localhost;dbname=shop;charset=utf8mb4´;
$user = ´root´;
$pass = ´´;
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
die(´Errore di connessione al database.´);
}
// Parametri paginazione
$itemsPerPage = 5;
$currentPage = isset($_GET[´page´]) ? max(1, (int) $_GET[´page´]) : 1;
$offset = ($currentPage - 1) * $itemsPerPage;
// Conteggio totale record
$totalStmt = $pdo->query(´SELECT COUNT(*) AS total FROM products´);
$totalRows = (int) $totalStmt->fetch()[´total´];
$totalPages = (int) ceil($totalRows / $itemsPerPage);
// Query paginata
$sql = ´SELECT id, name, price, created_at
FROM products
ORDER BY created_at DESC
LIMIT :limit OFFSET :offset´;
$stmt = $pdo->prepare($sql);
$stmt->bindValue(´:limit´, $itemsPerPage, PDO::PARAM_INT);
$stmt->bindValue(´:offset´, $offset, PDO::PARAM_INT);
$stmt->execute();
$products = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Elenco prodotti</title>
</head>
<body>
<h1>Prodotti</h1>
<?php if (empty($products)): ?>
<p>Nessun prodotto trovato.</p>
<?php else: ?>
<ul>
<?php foreach ($products as $product): ?>
<li>
<strong><?= htmlspecialchars($product[´name´]) ?></strong>
- €<?= number_format((float) $product[´price´], 2, ´,´, ´.´) ?>
<br>
Inserito il: <?= htmlspecialchars($product[´created_at´]) ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<nav>
<?php if ($currentPage > 1): ?>
<a href="?page=<?= $currentPage - 1 ?>">Pagina precedente</a>
<?php endif; ?>
<span> Pagina <?= $currentPage ?> di <?= max(1, $totalPages) ?> </span>
<?php if ($currentPage < $totalPages): ?>
<a href="?page=<?= $currentPage + 1 ?>">Pagina successiva</a>
<?php endif; ?>
</nav>
</body>
</html> Spiegazione
Il codice si divide in tre parti principali: connessione al database, calcolo dei parametri della paginazione e recupero dei dati.
1. Connessione con PDO
Usiamo PDO perché è più flessibile e sicuro rispetto a vecchie funzioni come mysql_query, ormai deprecate. Inoltre, impostiamo:
- PDO::ERRMODE_EXCEPTION, così gli errori vengono gestiti come eccezioni;
- PDO::FETCH_ASSOC, per ottenere array associativi più leggibili.
2. Calcolo della pagina corrente
La pagina arriva tramite $_GET[´page´]. Convertiamo il valore in intero e lo forziamo ad almeno 1 con max(1, ...). Questo evita valori negativi o input strani come testo o simboli.
Da qui otteniamo l’offset:
offset = (pagina corrente - 1) × elementi per pagina
Se siamo alla pagina 1 e mostriamo 5 elementi per volta, l’offset è 0. Alla pagina 2 l’offset è 5, alla pagina 3 è 10, e così via.
3. Conteggio totale dei record
Per costruire i link di navigazione dobbiamo sapere quante pagine esistono. Per questo usiamo:
SELECT COUNT(*) FROM products
Il risultato ci permette di calcolare il numero totale di pagine con ceil(), che arrotonda per eccesso. Se ci sono 12 prodotti e 5 elementi per pagina, avremo 3 pagine.
4. Query con LIMIT e OFFSET
La vera paginazione avviene nella query:
- LIMIT :limit definisce quanti record prendere;
- OFFSET :offset indica quanti record saltare.
È importante bindare questi valori come interi con PDO::PARAM_INT. Anche se in questo caso i parametri sono numerici, usare i placeholder mantiene il codice pulito e coerente con le best practice.
5. Output dei dati
Quando stampiamo i dati nel browser, usiamo htmlspecialchars() per evitare problemi di sicurezza legati all’iniezione di HTML o JavaScript nei campi testuali.
Per il prezzo usiamo number_format(), così il formato risulta più leggibile per l’utente italiano.
6. Navigazione tra le pagine
Il blocco finale mostra i link “Pagina precedente” e “Pagina successiva” solo quando servono. Questo rende l’interfaccia più pulita e impedisce di mostrare link inutili, ad esempio alla prima o all’ultima pagina.
Best practice
- Valida sempre la pagina richiesta: non fidarti mai direttamente dei parametri GET.
- Usa PDO con prepared statements: anche per query semplici, è una scelta più robusta.
- Ordina sempre i risultati: senza ORDER BY, la paginazione può diventare incoerente tra una richiesta e l’altra.
- Evita offset troppo grandi: su tabelle enormi, gli OFFSET elevati possono rallentare le query. In questi casi valuta una paginazione basata su ID o cursor-based pagination.
- Proteggi l’output HTML: usa htmlspecialchars() per ogni dato proveniente dal database che finisce nella pagina.
- Mostra un numero ragionevole di record per pagina: spesso 10, 20 o 25 sono valori equilibrati tra usabilità e performance.
Riepilogo
La paginazione è una tecnica essenziale quando lavori con MySQL/MariaDB in PHP. Ti permette di migliorare le prestazioni, rendere le pagine più leggibili e offrire una navigazione più comoda all’utente.
In questo tutorial hai visto come:
- connessione al database con PDO;
- calcolo della pagina corrente e dell’offset;
- conteggio totale dei record;
- recupero dei dati con LIMIT e OFFSET;
- costruzione di una navigazione semplice e sicura.
Se vuoi sviluppare applicazioni più professionali, la paginazione è uno di quei dettagli che fanno subito la differenza tra un progetto improvvisato e uno ben progettato.
Approfondisci con risorse ufficiali
- PHP Manual - PDO: documentazione ufficiale su connessioni, prepared statements e fetch mode.
- PHP Manual - htmlspecialchars(): utile per la protezione dell’output HTML.
- MySQL Reference Manual - SELECT: approfondimento su LIMIT e ORDER BY.
- MariaDB Knowledge Base: documentazione ufficiale per sintassi e comportamento delle query.
- PHP Manual - filter_var(): utile per validare dati in ingresso, come parametri numerici o stringhe.
