Introduzione
Quando si parla di form in PHP, la validazione non riguarda solo campi obbligatori o email corrette. Un sotto-argomento molto utile, spesso sottovalutato, è la validazione dei campi numerici con vincoli di business. In pratica, non basta verificare che un campo contenga un numero: bisogna controllare anche range, interi vs decimali, valori consentiti e coerenza tra più campi.
Questo tema è fondamentale in scenari reali come:
- quantità di prodotti in un ordine;
- età minima per registrarsi a un servizio;
- importi di pagamento o sconti;
- numero di posti prenotabili;
- punteggi, rating o percentuali.
Molti errori di applicazione nascono proprio da una validazione numerica troppo debole: un utente potrebbe inviare un valore negativo, un decimale dove serve un intero, oppure un numero fuori soglia. In questo tutorial vedremo come costruire una validazione robusta, semplice da mantenere e facile da estendere.
Codice completo
<?php
// Esempio: form per prenotare posti a un evento
// Campi:
// - nome
// - email
// - posti (intero tra 1 e 10)
// - età (intero minimo 18)
// - budget (numero decimale tra 10.00 e 500.00)
$errors = [];
$success = false;
// Valori di default per riempire il form dopo un errore
$nome = ´´;
$email = ´´;
$posti = ´´;
$eta = ´´;
$budget = ´´;
if ($_SERVER[´REQUEST_METHOD´] === ´POST´) {
// 1) Lettura e normalizzazione minima
$nome = trim($_POST[´nome´] ?? ´´);
$email = trim($_POST[´email´] ?? ´´);
$posti = trim($_POST[´posti´] ?? ´´);
$eta = trim($_POST[´eta´] ?? ´´);
$budget = trim($_POST[´budget´] ?? ´´);
// 2) Validazione nome
if ($nome === ´´) {
$errors[´nome´] = ´Il nome è obbligatorio.´;
} elseif (mb_strlen($nome) < 2) {
$errors[´nome´] = ´Il nome deve contenere almeno 2 caratteri.´;
}
// 3) Validazione email
if ($email === ´´) {
$errors[´email´] = ´L’email è obbligatoria.´;
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[´email´] = ´Inserisci un indirizzo email valido.´;
}
// 4) Validazione posti: intero tra 1 e 10
if ($posti === ´´) {
$errors[´posti´] = ´Specifica il numero di posti.´;
} elseif (filter_var($posti, FILTER_VALIDATE_INT) === false) {
$errors[´posti´] = ´I posti devono essere un numero intero.´;
} else {
$postiInt = (int) $posti;
if ($postiInt < 1 || $postiInt > 10) {
$errors[´posti´] = ´Puoi prenotare da 1 a 10 posti.´;
}
}
// 5) Validazione età: intero minimo 18
if ($eta === ´´) {
$errors[´eta´] = ´L’età è obbligatoria.´;
} elseif (filter_var($eta, FILTER_VALIDATE_INT) === false) {
$errors[´eta´] = ´L’età deve essere un numero intero.´;
} else {
$etaInt = (int) $eta;
if ($etaInt < 18) {
$errors[´eta´] = ´Devi avere almeno 18 anni.´;
}
}
// 6) Validazione budget: decimale tra 10.00 e 500.00
if ($budget === ´´) {
$errors[´budget´] = ´Il budget è obbligatorio.´;
} elseif (!is_numeric($budget)) {
$errors[´budget´] = ´Il budget deve essere un numero valido.´;
} else {
$budgetFloat = (float) $budget;
if ($budgetFloat < 10.00 || $budgetFloat > 500.00) {
$errors[´budget´] = ´Il budget deve essere compreso tra 10.00 e 500.00.´;
}
}
// 7) Validazione incrociata: più posti richiedono più budget minimo
if (!isset($errors[´posti´], $errors[´budget´])) {
if ((int)$posti >= 5 && (float)$budget < 50.00) {
$errors[´budget´] = ´Per 5 o più posti, il budget minimo è 50.00.´;
}
}
// 8) Se non ci sono errori, procedi
if (empty($errors)) {
$success = true;
// Qui potresti salvare nel database, inviare email, ecc.
// Per esempio:
// $stmt = $pdo->prepare("INSERT INTO prenotazioni ...");
// $stmt->execute([...]);
// In un caso reale potresti anche resettare i campi:
// $nome = $email = $posti = $eta = $budget = ´´;
}
}
function old(string $key, string $default = ´´): string
{
return htmlspecialchars($_POST[$key] ?? $default, ENT_QUOTES, ´UTF-8´);
}
function errorMessage(array $errors, string $key): string
{
return isset($errors[$key]) ? ´<p style="color:red;">´ . htmlspecialchars($errors[$key], ENT_QUOTES, ´UTF-8´) . ´</p>´ : ´´;
}
?>
<!doctype html>
<html lang="it">
<body>
<?php if ($success): ?>
<p>Prenotazione inviata con successo.</p>
<?php endif; ?>
<form method="post">
<p>
<label for="nome">Nome</label><br>
<input type="text" id="nome" name="nome" value="<?= old(´nome´, $nome) ?>">
<?= errorMessage($errors, ´nome´) ?>
</p>
<p>
<label for="email">Email</label><br>
<input type="email" id="email" name="email" value="<?= old(´email´, $email) ?>">
<?= errorMessage($errors, ´email´) ?>
</p>
<p>
<label for="posti">Posti</label><br>
<input type="number" id="posti" name="posti" min="1" max="10" step="1" value="<?= htmlspecialchars($posti, ENT_QUOTES, ´UTF-8´) ?>">
<?= errorMessage($errors, ´posti´) ?>
</p>
<p>
<label for="eta">Età</label><br>
<input type="number" id="eta" name="eta" min="18" step="1" value="<?= htmlspecialchars($eta, ENT_QUOTES, ´UTF-8´) ?>">
<?= errorMessage($errors, ´eta´) ?>
</p>
<p>
<label for="budget">Budget</label><br>
<input type="number" id="budget" name="budget" min="10" max="500" step="0.01" value="<?= htmlspecialchars($budget, ENT_QUOTES, ´UTF-8´) ?>">
<?= errorMessage($errors, ´budget´) ?>
</p>
<p>
<button type="submit">Invia</button>
</p>
</form>
</body>
</html> Spiegazione
Il cuore di questo esempio è la distinzione tra validazione sintattica e validazione semantica.
1. Validazione sintattica
Qui controlliamo se il dato “sembra” corretto dal punto di vista tecnico:
- un campo numerico contiene davvero un numero;
- un valore intero non contiene decimali;
- un’email ha una struttura accettabile.
Nel codice usiamo:
- filter_var(..., FILTER_VALIDATE_INT) per verificare gli interi;
- is_numeric() per numeri decimali o valori numerici più flessibili;
- filter_var(..., FILTER_VALIDATE_EMAIL) per l’email.
2. Validazione semantica
Un valore può essere tecnicamente valido ma comunque inaccettabile per il business. Per esempio:
- posti = 0 è un intero, ma non ha senso in una prenotazione;
- età = 12 è un numero valido, ma non rispetta il requisito minimo;
- budget = 5.00 è un numero valido, ma troppo basso rispetto ai vincoli del servizio.
Per questo, dopo il controllo del tipo, applichiamo i vincoli di range con confronti numerici.
3. Validazione incrociata
Un aspetto molto utile nei form reali è la validazione tra campi. Nell’esempio, se l’utente prenota 5 o più posti, il budget minimo richiesto sale. Questa logica non può essere verificata guardando un solo campo: bisogna confrontare il valore di posti con quello di budget.
Questo tipo di controllo è essenziale in checkout, prenotazioni, configuratori di prezzo e moduli con dipendenze tra campi.
4. Riempimento del form dopo l’errore
Le funzioni old() e errorMessage() migliorano l’esperienza utente:
- i campi rimangono compilati dopo un errore;
- i messaggi vengono mostrati vicino al campo sbagliato;
- si riduce la frustrazione e aumenta la chiarezza.
Nota importante: i valori vengono passati a htmlspecialchars() prima di essere stampati, così evitiamo problemi di XSS quando il dato torna nel markup HTML.
Best practice
- Valida sempre lato server, anche se usi attributi HTML come min, max e step. Il browser aiuta, ma non basta.
- Separa controllo del tipo e del contenuto: prima verifica che il valore sia numerico, poi applica i limiti.
- Usa messaggi specifici: “numero intero richiesto” è più utile di “valore non valido”.
- Non fidarti dei campi number del browser: possono essere manipolati o inviati manualmente con valori inattesi.
- Gestisci bene i decimali: per importi economici valuta l’uso di interi in centesimi oppure di librerie dedicate, per evitare errori di floating point.
- Applica i vincoli di business nel backend: l’interfaccia utente può suggerire limiti, ma la regola vera deve stare nel codice PHP.
- Evita duplicazioni: se un range viene usato in più punti, centralizzalo in costanti o configurazioni.
Riepilogo
La validazione dei campi numerici è molto più di un semplice “è un numero oppure no”. In un form PHP ben progettato devi controllare:
- se il valore è presente;
- se il tipo è corretto;
- se rientra nel range previsto;
- se rispetta le regole del dominio applicativo;
- se è coerente con gli altri campi del form.
Con un approccio strutturato come quello visto nell’esempio, il form diventa più affidabile, più sicuro e più facile da manutenere. Questa è una base solida per gestire prenotazioni, ordini, iscrizioni e qualunque flusso in cui i numeri abbiano un significato concreto.
Approfondisci con risorse ufficiali
- PHP Manual - filter_var(): documentazione ufficiale per la validazione dei dati.
- PHP Manual - is_numeric(): utile per verificare valori numerici.
- PHP Manual - htmlspecialchars(): fondamentale per stampare input utente in modo sicuro.
- MDN Web Docs - input type="number": per comprendere limiti e comportamento dei campi numerici nel browser.
- OWASP Cheat Sheet Series: per approfondire sicurezza, validazione server-side e difesa dagli input malevoli.
