Introduzione
Quando si lavora con un database in PHP, spesso non serve leggere una lista di righe: basta recuperare un solo record. Può essere il profilo di un utente, i dettagli di un prodotto, la configurazione di un sito o il risultato di una ricerca univoca. In questi casi, usare PDO in modo corretto evita codice ridondante, controlli inutili e problemi di sicurezza.
Un errore comune è trattare una query che deve restituire un solo record come se fosse una lista. Questo porta a codice meno leggibile e, a volte, a bug difficili da individuare. In questo tutorial vediamo un approccio pratico per:
- eseguire una query parametrizzata con PDO;
- verificare se il record esiste;
- leggere un solo risultato in modo chiaro;
- gestire il caso in cui il record non venga trovato.
L’obiettivo è scrivere codice semplice, robusto e adatto a contesti reali.
Codice completo
<?php
declare(strict_types=1);
// Dati di connessione al database
$dsn = ´mysql:host=localhost;dbname=shop;charset=utf8mb4´;
$username = ´root´;
$password = ´´;
// Opzioni consigliate per PDO
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $username, $password, $options);
// Simuliamo un input proveniente da URL o form
$userId = filter_input(INPUT_GET, ´id´, FILTER_VALIDATE_INT);
if ($userId === false || $userId === null) {
throw new InvalidArgumentException(´ID utente non valido.´);
}
// Query per recuperare un solo record
$sql = "
SELECT id, nome, email, created_at
FROM utenti
WHERE id = :id
LIMIT 1
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
´:id´ => $userId
]);
// Fetch singolo record
$utente = $stmt->fetch();
if ($utente === false) {
echo "<p>Utente non trovato.</p>";
} else {
echo "<h2>Dettagli utente</h2>";
echo "<p><strong>ID:</strong> " . htmlspecialchars((string)$utente[´id´]) . "</p>";
echo "<p><strong>Nome:</strong> " . htmlspecialchars($utente[´nome´]) . "</p>";
echo "<p><strong>Email:</strong> " . htmlspecialchars($utente[´email´]) . "</p>";
echo "<p><strong>Registrato il:</strong> " . htmlspecialchars($utente[´created_at´]) . "</p>";
}
} catch (PDOException $e) {
// In produzione sarebbe meglio registrare l´errore su log e non mostrarlo all´utente
echo "<p>Errore database: " . htmlspecialchars($e->getMessage()) . "</p>";
} catch (Throwable $e) {
echo "<p>Errore applicativo: " . htmlspecialchars($e->getMessage()) . "</p>";
}
Spiegazione
Vediamo il codice passo per passo. L’idea centrale è: se sai che la query deve restituire al massimo una riga, progetta tutto il flusso per quel caso specifico.
1. Connessione PDO configurata bene
La connessione usa un DSN MySQL con charset utf8mb4, che è la scelta corretta per supportare emoji e caratteri internazionali. Inoltre, sono state impostate alcune opzioni molto utili:
- PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION: gli errori diventano eccezioni, quindi il controllo è più chiaro;
- PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC: i risultati vengono restituiti come array associativi;
- PDO::ATTR_EMULATE_PREPARES => false: si preferiscono i prepared statement nativi del driver quando possibile.
2. Validazione dell’input
Prima di interrogare il database, il parametro id viene validato con filter_input(). Questo è importante perché non bisogna mai fidarsi dei dati in ingresso, anche se sembrano semplici numeri.
Se l’ID non è valido, il codice interrompe il flusso con un’eccezione. In un progetto reale potresti mostrare un messaggio più elegante o reindirizzare l’utente.
3. Query con LIMIT 1
La query include LIMIT 1. Anche se il campo id è normalmente univoco, questa clausola rende esplicita l’intenzione: vogliamo un solo record.
Questo è utile per due motivi:
- rende il codice più leggibile;
- aiuta il database a fermarsi al primo risultato utile.
4. Prepared statement e placeholder
Il valore dell’ID viene passato tramite un placeholder nominativo :id. In questo modo PDO separa la query dai dati, migliorando sicurezza e manutenzione.
È una pratica fondamentale perché evita concatenazioni manuali e riduce il rischio di SQL injection. Anche quando stai recuperando un solo record, il principio non cambia.
5. fetch() invece di fetchAll()
Qui sta il punto più importante del tutorial: per leggere un solo record, usa fetch(), non fetchAll().
fetch()restituisce una singola riga alla volta;fetchAll()restituisce un array con tutte le righe trovate.
Se sai già che ti serve un solo risultato, fetch() è più adatto perché:
- usa meno memoria;
- produce un codice più chiaro;
- semplifica il controllo del caso “record non trovato”.
Se la query non restituisce righe, fetch() ritorna false. Questo rende molto semplice il controllo:
if ($utente === false) {
echo "Utente non trovato.";
} 6. Output sicuro con htmlspecialchars()
Anche se i dati provengono dal database, è buona norma stamparli con htmlspecialchars(). In questo modo eviti problemi se il contenuto contiene caratteri speciali o testo potenzialmente pericoloso per l’HTML.
Questa è una difesa importante contro l’XSS, soprattutto quando mostri dati inseriti dagli utenti.
Best practice
Quando recuperi un solo record con PDO, ci sono alcune regole pratiche da seguire per scrivere codice professionale.
- Usa sempre prepared statement anche per query semplici.
- Preferisci fetch() se ti serve una sola riga.
- Aggiungi LIMIT 1 quando la query deve restituire un solo risultato.
- Valida gli input prima della query, soprattutto ID, email e date.
- Non mostrare messaggi di errore completi in produzione: meglio registrarli nei log.
- Escape dell’output con htmlspecialchars() ogni volta che stampi dati nel browser.
- Usa tipi coerenti: se l’ID è intero, validalo come intero e non come stringa generica.
Un altro consiglio utile è separare la logica di accesso ai dati dalla presentazione. In un progetto più strutturato, potresti mettere la query dentro una funzione o in un repository, così la pagina si limita a mostrare il risultato.
Per esempio, invece di scrivere tutto in un file unico, puoi creare una funzione come questa:
function trovaUtentePerId(PDO $pdo, int $id): ?array
{
$sql = "SELECT id, nome, email FROM utenti WHERE id = :id LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([´:id´ => $id]);
$utente = $stmt->fetch();
return $utente === false ? null : $utente;
} Questo approccio rende il codice più riutilizzabile e più facile da testare.
Riepilogo
Recuperare un solo record con PDO è un’operazione comune, ma va gestita con attenzione. Il flusso corretto è semplice:
- validi l’input;
- usi una query parametrizzata;
- aggiungi
LIMIT 1; - leggi il risultato con
fetch(); - verifichi se il record esiste;
- stampi i dati in modo sicuro.
Seguendo questi passaggi ottieni codice più leggibile, più sicuro e più efficiente. È un piccolo dettaglio, ma in applicazioni reali fa una grande differenza.
Approfondisci con risorse ufficiali
- PHP Manual - PDO: documentazione ufficiale della classe PDO e delle sue opzioni.
- PHP Manual - PDOStatement::fetch(): per capire bene come leggere una singola riga.
- PHP Manual - Prepared Statements: spiegazione ufficiale su query preparate e parametri.
- PHP Manual - filter_input(): utile per validare i dati in ingresso.
- PHP Manual - htmlspecialchars(): fondamentale per l’output sicuro in HTML.
