PHP, form e validazione: verificare l’unicità di username ed e-mail

by theArchitect
SHARE
PHP, form e validazione: verificare l’unicità di username ed e-mail
© Guida-HTML5.it

Introduzione

Quando si realizza un modulo di registrazione, controllare che i dati abbiano un formato corretto non è sufficiente. Un indirizzo e-mail può essere formalmente valido, ma potrebbe essere già associato a un altro account. Allo stesso modo, uno username può rispettare la lunghezza richiesta e risultare comunque già presente nel database.

Questo tipo di controllo viene chiamato validazione di unicità e deve essere eseguito lato server. La verifica JavaScript nel browser può migliorare l’esperienza dell’utente, ma non è affidabile come unica protezione: una richiesta HTTP può essere inviata manualmente senza passare dal codice JavaScript.

Nell’esempio costruiremo un form PHP per la registrazione di un utente. Verificheremo:

  • che la richiesta utilizzi il metodo POST;
  • che username ed e-mail rispettino formato e lunghezza;
  • che username ed e-mail non siano già presenti;
  • che la password venga memorizzata con password_hash();
  • che il database venga interrogato con istruzioni preparate PDO.

Codice completo

Il seguente esempio presuppone una tabella MySQL simile a questa:

CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(30) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Il file registrazione.php contiene sia la logica di validazione sia il modulo HTML:

<?php
$pdo = new PDO(
    ´mysql:host=localhost;dbname=app;charset=utf8mb4´,
    ´app_user´,
    ´password´,
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
);

$errors = [];
$old = [
    ´username´ => ´´,
    ´email´ => ´´
];

if ($_SERVER[´REQUEST_METHOD´] === ´POST´) {
    // Recuperiamo i valori e rimuoviamo gli spazi esterni.
    $username = trim($_POST[´username´] ?? ´´);
    $email = trim($_POST[´email´] ?? ´´);
    $password = $_POST[´password´] ?? ´´;

    // Conserviamo soltanto i dati non sensibili.
    $old[´username´] = $username;
    $old[´email´] = $email;

    // Validazione dello username.
    if ($username === ´´) {
        $errors[´username´] = ´Lo username è obbligatorio.´;
    } elseif (!preg_match(´/^[A-Za-z0-9_]{4,30}$/´, $username)) {
        $errors[´username´] =
            ´Usa da 4 a 30 caratteri: lettere, numeri o underscore.´;
    }

    // Validazione dell’e-mail.
    if ($email === ´´) {
        $errors[´email´] = ´L’indirizzo e-mail è obbligatorio.´;
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[´email´] = ´Inserisci un indirizzo e-mail valido.´;
    } elseif (mb_strlen($email) > 255) {
        $errors[´email´] = ´L’e-mail è troppo lunga.´;
    }

    // Validazione della password.
    if (strlen($password) < 8) {
        $errors[´password´] =
            ´La password deve contenere almeno 8 caratteri.´;
    }

    // Eseguiamo il controllo sul database solo se i formati sono corretti.
    if (!$errors) {
        $query = $pdo->prepare(
            ´SELECT username, email
             FROM users
             WHERE username = :username OR email = :email
             LIMIT 1´
        );

        $query->execute([
            ´username´ => $username,
            ´email´ => $email
        ]);

        $existingUser = $query->fetch();

        if ($existingUser) {
            if (strcasecmp($existingUser[´username´], $username) === 0) {
                $errors[´username´] =
                    ´Questo username è già utilizzato.´;
            }

            if (strcasecmp($existingUser[´email´], $email) === 0) {
                $errors[´email´] =
                    ´Questa e-mail è già associata a un account.´;
            }
        }
    }

    if (!$errors) {
        $passwordHash = password_hash($password, PASSWORD_DEFAULT);

        $insert = $pdo->prepare(
            ´INSERT INTO users (username, email, password_hash)
             VALUES (:username, :email, :password_hash)´
        );

        try {
            $insert->execute([
                ´username´ => $username,
                ´email´ => $email,
                ´password_hash´ => $passwordHash
            ]);

            echo ´<p>Registrazione completata.</p>´;
            exit;
        } catch (PDOException $exception) {
            // La UNIQUE del database protegge anche da richieste simultanee.
            $errors[´general´] =
                ´Non è stato possibile completare la registrazione.´;
        }
    }
}
?>

<form method="post" action="">
    <label for="username">Username</label>
    <input
        type="text"
        id="username"
        name="username"
        maxlength="30"
        value="<?= htmlspecialchars($old[´username´], ENT_QUOTES, ´UTF-8´) ?>"
        required
    >
    <?php if (isset($errors[´username´])): ?>
        <p><?= htmlspecialchars($errors[´username´], ENT_QUOTES, ´UTF-8´) ?></p>
    <?php endif; ?>

    <label for="email">E-mail</label>
    <input
        type="email"
        id="email"
        name="email"
        maxlength="255"
        value="<?= htmlspecialchars($old[´email´], ENT_QUOTES, ´UTF-8´) ?>"
        required
    >
    <?php if (isset($errors[´email´])): ?>
        <p><?= htmlspecialchars($errors[´email´], ENT_QUOTES, ´UTF-8´) ?></p>
    <?php endif; ?>

    <label for="password">Password</label>
    <input type="password" id="password" name="password" required>
    <?php if (isset($errors[´password´])): ?>
        <p><?= htmlspecialchars($errors[´password´], ENT_QUOTES, ´UTF-8´) ?></p>
    <?php endif; ?>

    <?php if (isset($errors[´general´])): ?>
        <p><?= htmlspecialchars($errors[´general´], ENT_QUOTES, ´UTF-8´) ?></p>
    <?php endif; ?>

    <button type="submit">Registrati</button>
</form>

Spiegazione

Validazione prima del database

Il codice controlla prima i dati in memoria e interroga il database soltanto quando username, e-mail e password rispettano le regole di base. Questo evita query inutili e separa due responsabilità diverse: la validazione del formato e la verifica dell’esistenza di un record.

La regex dello username accetta esclusivamente lettere, numeri e underscore. La funzione filter_var() verifica invece la struttura generale dell’indirizzo e-mail. Questi controlli non garantiscono che la casella esista davvero, ma impediscono molti input evidentemente errati.

Perché usare vincoli UNIQUE

Il controllo con SELECT è utile per mostrare un messaggio comprensibile all’utente, ma non è sufficiente da solo. Due richieste simultanee potrebbero entrambe verificare che l’e-mail sia libera e tentare poi l’inserimento.

Per questo la tabella contiene i vincoli UNIQUE. Il database diventa l’ultima barriera contro i duplicati. Il blocco try/catch gestisce eventuali errori generati durante l’inserimento.

Password e dati restituiti al form

La password non viene mai inserita direttamente nel database. password_hash() crea un hash sicuro comprensivo di salt. Durante il login si userà password_verify().

Username ed e-mail vengono invece riproposti nel form quando la validazione fallisce. La funzione htmlspecialchars() impedisce che un valore inviato dall’utente venga interpretato come HTML nel browser.

Best practice

  • Usa sempre PDO con istruzioni preparate per evitare SQL injection.
  • Mantieni i vincoli UNIQUE nel database, anche se esegui un controllo preliminare.
  • Non memorizzare mai password in chiaro e non ripopolare il campo password dopo un errore.
  • Normalizza con attenzione le e-mail. Se l’applicazione considera maiuscole e minuscole equivalenti, applica una regola coerente sia nella ricerca sia nel salvataggio.
  • Non mostrare dettagli tecnici di PDO agli utenti finali: registra l’errore nei log del server.
  • In produzione usa credenziali del database conservate in variabili d’ambiente, non direttamente nel codice.
  • Proteggi il modulo anche con HTTPS e, per applicazioni reali, aggiungi un token CSRF.
  • Considera un limite di tentativi per ridurre abusi e richieste automatizzate.

Riepilogo

La validazione di un form di registrazione deve combinare controlli di formato, verifiche sul database e vincoli strutturali. PHP controlla i dati ricevuti, PDO esegue query sicure e MySQL garantisce che username ed e-mail non vengano duplicati nemmeno in caso di richieste simultanee.

La regola più importante è non fidarsi mai del browser: gli attributi HTML come required e type="email" migliorano l’interfaccia, ma la validazione decisiva deve sempre avvenire sul server.

Approfondisci con risorse ufficiali

SHARE