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.
