[PILLOLA] Refresh pagina con form e duplicazione dati nel db
Come evitare il problema della duplicazione dati
Puntualmente si ripresenta nei thread il quesito su come evitare che uno user
possa semplicemente con un refresh della pagina (F5) inserire piu' e piu' volte
lo stesso dato, creando innumerevoli record duplicati.
Poiche' non e' semplice spiegare il perche' ed il percome ad un novizio del PHP,
ho pensato di mettere a disposizione un esempio su come evitare il problema.
La soluzione piu' semplice ed immediata sarebbe quella di rinviare ad altra pagina
che provveda con un redirect a rimandarci sulla pagina dove e' situato il form
di immissione dati. In questo modo la cache del browser non sarebbe piu' valida e
$_POST o $_GET svuotati del loro contenuto.
Proviamo invece a considerare di rimanere sempre e solo con una pagina di immissione
dati come richiesto da alcuni. Il modo che viene ora proposto puo' anche essere
suddiviso in due pagine, ma in questo caso occorrera' provvedere dalla seconda pagina
il rimando alla prima in caso di mancanza di dati o di refresh dei dati del form.
Per evitare il reinserimento dei dati ci sono piu' possibilita':
1) Fare un SELECT sull'ultimo id inserito e verificare se i dati da immettere sono
identici a quelli presenti in $_POST o $_GET.
2) Tramite la sessione valorizzare una variabile stringa da passare con $_SESSION
dopo l'esito positivo della INSERT da verificare poi all'ingresso della pagina.
Se sara' presente la stringa significhera' che il dato e' gia' stato inserito.
Il primo metodo non e' flessibile, sarebbe dipendente dal tipo di campo, univocita',
numerosita' e potrebbe essere dispendioso controllare anche decine di campi.
Inoltre richiede sempre una query preventiva.
Il secondo metodo impedirebbe addirittura di inserire ulteriori dati se non chiudendo
e riaprendo il browser. Altrimenti necessita un redirect ad una pagina appoggio che
unsetti la variabile di sessione e rimandi alla pagina del form.
Necessita quindi un sistema sicuro, semplice e facilmente utilizzabile, in altre
parole "standard". Cioe' dove lo metto lo metto e lui funziona senza altro chiedere.
Proviamo quindi il seguente script che verra' commentato man mano. Intanto se volete
provare senza fare cambiamenti allo script, potete configurare la seguente tabella
da mettere nel db "TEST" o dove vi pare aggiornando pero' lo script se messa altrove.
codice:
CREATE TABLE `tabella1` (
`id` int(4) unsigned NOT NULL auto_increment,
`titolo` varchar(255) NOT NULL default '',
`autore` varchar(50) NOT NULL default '',
`note` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`)
)
Ora lo script con nome file: quello_che_vuoi.php.
codice:
<?php
session_start();
// verifichiamo se $_SESSION e' valorizzato. Se lo e', trasferiamo il contenuto
// in $check altrimenti settiamo $check = vuoto. Se $_SESSION non esiste significa
// che e' la prima volta che viene eseguito lo script.
isset($_SESSION['check']) ? $check = $_SESSION['check'] : $check = '';
// valorizziamo ora una variabile "$caso" con un dato casuale non ripetibile
// Questa variabile trasmessa in modo hidden dal form ci permettera' di riconoscere
// quando il dato in POST viene trasmesso da un refresh (sara' identico) oppure
// da un form (sara' diverso) e permettera' allo user di reinserire gli stessi dati
// ma da form e non per refresh della pagina. Se questa duplicazione non fosse ammessa
// sara' sufficiente rimuovere dal form la riga con:
// <input type = "hidden" name = "check" value = "$caso">
$caso = microtime();
// Prepariamo il form con la sintassi heredoc e lo mettiamo in $str .
// Assicuriamoci di non avere spazi dopo la fine della prima e dell'ultima riga.
// Un return secco.
$str = <<<FORM
<html><body>
<form method = "POST" action = "$_SERVER[PHP_SELF]">
Prego inserire i dati. I campi con * sono obbligatori.
* Titolo <input type= "text" name = "titolo">
* Autore <input type= "text" name = "autore">
Note <input type= "text" name = "note">
<input type = "hidden" name = "check" value ="$caso">
<input type = "submit" name = "invia" value = "- Invia -">
</body></html>
FORM;
// verifichiamo ora se $_POST esiste e che non sia vuoto. Sara' da fare per tutti i campi
// che devono essere compilati in modo obbligatorio. Se i campi non sono compilati o
// $_POST non esistente verremo inviati al FORM di immissione presente nell' ELSE.
if(isset($_POST['titolo'], $_POST['autore']) AND $_POST['titolo']!='' AND $_POST['autore']!='')
{
// Se siamo arrivati qui significa che $_POST c'e' e i dati pure. Verifichiamo se l'hash
// prodotto da MD5($_POST) e' identico a quello passato con $_SESSION e trasferito in $chek.
// utilizziamo serialize() perche' essendo $_POST un array, riceveremmo un NOTICE .
if($check === MD5(serialize($_POST)) ) {
echo "
Dati gia immessi - ciao ciao";
echo $str;
exit;
} else {
// Poiche' dobbiamo mantenere integro il contenuto di $_POST
// per poterlo confrontare, eventuali controlli sulle stringhe li faremo
// generando nuove variabili. Usiamo come esempio TRIM() ma potrebbe
// essere qualunque la funzione di controllo utilizzata.
$titolo = trim($_POST['titolo']);
$autore = trim($_POST['autore']);
$note = htmlentities(trim($_POST['note']));
// Ora inseriamo i dati nel db. Inserite la vostra connessione
require "./config.inc.php";
mysql_select_db('test');
mysql_query("INSERT INTO tabella (titolo, autore, note)
VALUES ('$titolo','$autore', '$note')");
// verifichiamo la riga inserita
$num = mysql_affected_rows();
// Se la riga e' inserita mandiamo un avviso e settiamo l'hash
// nell'array di sessione. Oppure avvisiamo del fallimento.
// in questo ultimo caso notate che l'hash non viene prodotto e
// che ripresentiamo comunque il form in entrambi i casi.
if($num == 1) {
echo "
Dati inseriti
";
$_SESSION['check'] = MD5(serialize($_POST));
echo $str;
exit;
}
else {
echo "
Dati NON inseriti - Riprova";
echo $str;
exit;
}
}
// nel caso non fosse settato $_POST perche' e' la prima volta che viene eseguita la pagina
// oppure $POST settato ma con dei campi vuoti. presentiamo o ripresentiamo il form.
} else {
echo $str;
}
?>
Molto semplice ma efficace. Ovviamente uno user potra' inserire tutte le stringhe identiche
che vorra' se questo e' lecito o necessario.
In pratica serve ad impedire che il refresh reinserisca lo stesso dato o che lo user per
errore reinserisca lo stesso dato per distrazione, opzione questa ottenibile
togliendo la riga con INPUT TYPE = HIDDEN come detto nel commento iniziale.
Se si vuole invece l'univocita' del dato o di piu' dati inseriti, il controllo dovra'
tener conto di tutta la tabella e non solo dell'ultimo inserimento fatto.
Non e' questo il nostro scopo.
I coockies devono essere abilitati (per uso della sessione), e le sessioni funzionare.
Come avete potuto riscontrare si tratta di utilizzare 4-5 righe di script assolutamente
standardizzate, cioe' uguali per qualunque form di inserimento che utilizza una o piu' pagine.
Mi sembra sia tutto facile e self-explanatory ... ma nel caso servano chiarimenti
non esitate a chiedere oppure a suggerire soluzioni alternative.
Ciao.