PHP, form e validazione: controllare i campi numerici con vincoli reali

by theArchitect
SHARE
PHP, form e validazione: controllare i campi numerici con vincoli reali
© Guida-HTML5.it

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.

SHARE