PHP e MySQL/MariaDB: paginazione dei risultati in modo efficiente

by theArchitect
SHARE
PHP e MySQL/MariaDB: paginazione dei risultati in modo efficiente
© Guida-HTML5.it

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.

SHARE