Una pregunta frecuente es cómo evitar que cuando un usuario a hecho una operación que modifica la base de datos, se pueda evitar que esta operación se repita si el usuario, inseguro de su resultado, pide un refresco de la página o, después de haber navegado a otros lugares, pase accidentalmente por esta página al retroceder.

Debemos primero comprender dónde está el problema. El navegador mantiene una lista de los lugares visitados en su historial. Cuando se pide un refresco, el navegador va a la dirección más reciente de esta lista, cuando se vuelve atrás, se va retrocediendo en las direcciones de esta lista. El valor almacenado no sólo es la dirección de la página sino también todos los datos que se hubieran enviado, ya fuera por GET o POST, por lo tanto, cuando la operación se repite, se repite en su totalidad, con todos sus datos y todas sus consecuencias.

Desde PHP no se puede hacer mucho por controlar esta lista una vez que una dirección ingresó en ella, pero sí podemos engañar al navegador para que no ingrese esa dirección, y sus datos, sino otra que nosotros le indiquemos. Esto lo hacemos mediante la función header(), específicamente enviándole el header `Location: `. Este header le indica al navegador que la página solicitada ya no existe y que está en una nueva ubicación, la que se le indica con ese encabezado. El navegador, a fin de acelerar futuros accesos a la página pedida, en lugar de guardar la dirección original, que supone obsoleta, guarda en el historial la nueva, que supone es la correcta de allí en más.

El ejemplo muestra cómo funciona este mecanismo. Para evitar usar tablas en base de datos, lo que hemos hecho es usar una variable de sesión (array $_SESSION) que vamos incrementando. Para simular que esta operación funcione o falle, como podría ocurrir con una operación de SQL, le damos la opción al usuario para generar un éxito o un fracaso a través de sendos botones.

Si el usuario pulsa el botón Funciona, el contador en $_SESSION['contador'] se incrementará en uno y se mostrará un mensaje confirmando el éxito de la operación. Si el usuario pulsa el botón No funciona, el programa hará como si hubiera habido un fallo.

Debajo de los botones se muestra el valor del contador, que inicialmente estará en blanco. Al pulsar el botón Funciona, se incrementará en uno. Al pulsar el botón No Funciona, no se incrementará pues, supuestamente, habría habido un error. Lo importante es que en el caso de pedir un refresco de la página tanto luego de que funcione como de que no, el contador no cambiará.

Al principio del script, tras iniciar la sesión (función session_start(), sino el array $_SESSION no mantiene los valores entre accesos) defino un par de funciones redireccion_relativa() y redireccion_aqui(). Estas funciones arman un header del tipo 'Location: ` para forzar una redirección. La primera de las funciones hace una redirección relativa a la página actual, la segunda hace una redirección a sí misma. Ambas admiten un par de argumentos que permiten armar un URL con argumentos, usando la función http_build_query(). La primera de estas funciones no se usa en el ejemplo, la incluí como `tip´. Para completar las posibilidades, faltan las funciones redireccion_absoluta() y redireccion_raiz(), la primera es trivial, va donde le digas. La segunda sirve para ir a una página relativa a la raíz de la aplicación. Esta función depende de una variable externa a esta página que indique la dirección raíz de la aplicación. Esta variable usualmente estará guardada en un archivo de configuración de la aplicación o en un registro de la base de datos. Por depender de variables externas al ejemplo, no la incluí, pero es fácil hacerla a partir de las dadas.

Tras estas funciones comienza el proceso en sí. Noten que hasta este punto no se ha enviado al navegador ni un solo carácter. Esto es importante, el header `Location: ` no funciona si ya se ha enviado algo y da error, así que es importante que no haya ni siquiera un carácter en blanco. Vale la pena mencionar aquí algo que hace que este error salga sin que uno lo quiera. Aunque uno haya buscado hasta el aburrimiento que no hubiera caracteres extra (por ejemplo, no debe haber nada antes del primer <?php) el error aparece. Esto suele ocurrir cuando la página incluye otros scripts a través de la instrucción include(). Si alguno cualquiera de estos archivos incluidos tienen aunque más no fuera una interlínea después del último ?> de cierre, ese carácter se enviará al navegador y generará error. En ocasiones, el editor de texto que usamos agrega automáticamente una interlínea al final del archivo sin preguntar. La forma de evitar este error es simplemente dejar el archivo incluído sin el ?> final. Esto no genera error y, de hecho, es la sugerencia de Rasmus Lerdorf, quien ha de saber lo que hace.Volviendo al programa, en caso de recibir un argumento de nombre 'submit' verifico que el valor sea 'Funciona', en ese caso, incremento el contador y hago una redirección a esta misma página pero cambiando los argumentos. No quiero que haya un argumento de nombre 'submit' con valor 'Funciona' pues esto haría que el contador se volviera a incrementar. Invento otro argumento, de nombre 'confirma' y valor 'ok'. Es este el valor que el navegador recibirá como nueva ubicación de esta página y la que guardará en su historial. De hecho, si se mira en la casilla de dirección del navegador, se verá que el URL que muestra es el de la página de confirmación, no el del formulario.

En el caso de error, no hago ninguna redirección, lo habitual es que el form se vuelva a mostrar con los valores que el usuario hubiera ingresado, que se obtendrán de las variables $_GET o $_POST según se hubiera indicado en el formulario.

El resto del programa no requiere mayor explicación que la que dan los comentarios que contiene.