Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Question de debutant sur formulaire

40 réponses
Avatar
Marc Mendez
Bonjour,

J'ai un formulaire en HTML , tout simple, qui permet par exemple d'ajouter
un élément dans un base de données.
Je passe par un post qui appelle la même page. Le choix du traitement est
conditionné par un paramètre qui indique ce que je veux faire (add, del,
update).
Tout marche très bien. Sauf que si je fais un refresh dans le navigateur de
la page, suite à une action, il me rebalance le même traitement !
Il faudrait en fait, une fois que mon traitement est effectué, que j'efface
le contenu des paramètres..... commen faire ? ou y-a-t-il un autre moyen ?

J'espère avoir été assez clair....

merci de votre aide.

10 réponses

1 2 3 4
Avatar
David JOURAND
Le Thu, 14 Sep 2006 08:50:57 +0000, John GALLET a écrit :

En toute logique, le stockage à long terme ce ce jeton est totalement
inutile et c'est une logique inverse qu'il faut appliquer : on génère un
jeton d'autorisation qu'on stocke pour XX minutes (time out). S'il est
toujours valide quand la requête arrive, on exécuté la requête, sinon
elle a déjà été exécutée (ou est invalide car time-outée) et on ne
fait rien. Et c'est encore la base : on gère des listes de choses
autorisées, pas des listes de choses interdites.


Cela rejoint tout à fait ce que j'écrivais : l'UID est un jeton
d'autorisation, que l'on génère donc quand on sert le formulaire la
première fois (en GET généralement). De même je ne le stocke pas dans la
base car cela n'a effectivement aucun intérêt, mais dans la session. Par
contre je ne vois pas l'intérêt de mettre un timeout.

Enfin bref, on gère manuellement une session quoi... (je disais quoi
récemment sur "répéter la même chose" ?)


Pas tout à fait. Je m'appuie sur la session pour gérer une sorte de
/mini session/ sur le formulaire. Je connais votre opinion sur les
sessions, mais je trouve votre position un peu extremiste.

Que ce soit par hidden, par paramètre dans un lien <a href>, par cookie
ou par pigeon voyageur, peu importe le mode de transmission, çà
marchera pareil.


Oui mais avec mon mécanisme, je l'ai codé une fois pour toute, et je ne
me souci plus de cela (tant qu'aucun bug/faille n'a été détectée)
lorsque je créer un nouveau formulaire : tout est automatique.

--
David JOURAND - http://www.numabilis.com
Supprimer "site." et ".invalid" de mon adresse mail pour me répondre.

Avatar
John GALLET
De même je ne le stocke pas dans la
base car cela n'a effectivement aucun intérêt, mais dans la session. Par
contre je ne vois pas l'intérêt de mettre un timeout.


Dès lors qu'il est lié à la session strictement aucun intérêt vu qu'il
(le jeton) l'hérite de la session. Ensuite tout dépend du ménanisme
utilisé pour gérer la session.

En revanche, selon ce mécanisme là, il me semble qu'il vaudrait mieux
lier à la session un flag de type "formulaire machin déjà traité
correctement" et non un ID aléatoire, ce serait plus logique. En gros
une variable d'état du formulaire, qui est initialisée à "jamais
demandé" (ou alors c'est son absence qui en fait office), puis passe à
"formulaire généré, pas encore de réception de données", puis à "données
soumises avec erreurs empêchant le traitement" si nécessaire (on peut
boucler sur cet état) et ensuite à "formulaire traité avec succès,
ignorer toute nouvelle requête le concernant". Enfin moi je le
traiterais plutôt comme ça si on a une session déjà disponible.

votre opinion sur les sessions,
Il n'y a pas que la gestion manuelle en dehors de tout mécanisme de PHP

qui peut poser des problèmes, dès lors qu'on se sert de
session_set_save_handler() par exemple on est entre les deux.


JG

Avatar
David JOURAND
Le Thu, 14 Sep 2006 12:23:09 +0000, John GALLET a écrit :

En revanche, selon ce mécanisme là, il me semble qu'il vaudrait mieux
lier à la session un flag de type "formulaire machin déjà traité
correctement" et non un ID aléatoire, ce serait plus logique.


C'est exactement comme cela que je gère le mécanisme (l'identifiant
unique UID n'est pas une suite alphanumérique aléatoire, mais l'url du
formulaire).


--
David JOURAND - http://www.numabilis.com
Supprimer "site." et ".invalid" de mon adresse mail pour me répondre.

Avatar
Michel Billaud
John GALLET writes:

Tu as deux grands types de solutions, parfaitement combinables d'ailleurs.

1) le jeton unique, comme rappelé par exemple par D. Jourand dans ce
2) comprendre comment on doit utiliser une clef primaire ou une clef


Une troisieme: considérer qu'en recevant les données du formulaire, il
y a deux choses à faire
1) les traiter (ajout / retrait dans la base)
2) afficher quelque chose

Scinder ça en deux étapes
* la premiere fait le traitement, et fait une redirection sur la seconde
* la seconde fait uniquement un affichage.

Si l'utilisateur demande à réafficher la page, le traitement n'est pas relancé.

Ce genre de chose (à la hache):

$etape = $_POST['etape'];
if (! $action) $action = "grille";

switch ($action) {
case "grille" :
print '<form action="truc.php?etape=traiter" method=POST>'
print '<input type=text name=nom size >';
....
break;
case "traiter" :
$nom = $_POST['nom'];
ajouter_dans_base($nom)
header("Location: truc.php?etape¯fichersucces&nom=$nom")
exit;
break;
case "afficher" :
$nom = $_GET['nom'];
print "<h1>Succes</h1> On a bien ajoute $nom";
break;
}

MB
--
Michel BILLAUD
LABRI-Université Bordeaux I tel 05 4000 6922 / 05 5684 5792
351, cours de la Libération http://www.labri.fr/~billaud
33405 Talence (FRANCE)

Avatar
Olivier Miakinen

Tu as deux grands types de solutions, parfaitement combinables d'ailleurs.

[ snip les bonnes solutions ]


Une troisieme: considérer qu'en recevant les données du formulaire, il
y a deux choses à faire
1) les traiter (ajout / retrait dans la base)


Sans contrôler que le traitement ne soit pas fait deux fois, au cas où
tu recevrais deux fois la même requête ?

2) afficher quelque chose

Scinder ça en deux étapes
* la premiere fait le traitement, et fait une redirection sur la seconde


Aaaargh !

* la seconde fait uniquement un affichage.

Si l'utilisateur demande à réafficher la page, le traitement n'est pas relancé.


Non, mais si l'utilisateur refait la même requête (que ce soit après
avoir cliqué sur le bouton back ou par une boucle de WGET), le
traitement *est* relancé.

Ce genre de chose (à la hache):

[...]
header("Location: truc.php?etape¯fichersucces&nom=$nom")


Combien de fois faudra-t-il répéter qu'une URL dans un Location: doit
être complète et pas relative ? Et qu'un header("Location... ") ne
résoudra jamais les problèmes de rejeu ?


Avatar
David JOURAND
Le Fri, 15 Sep 2006 17:31:33 +0000, Michel Billaud a écrit :

Scinder ça en deux étapes
* la premiere fait le traitement, et fait une redirection sur la
seconde * la seconde fait uniquement un affichage.

Si l'utilisateur demande à réafficher la page, le traitement n'est pas
relancé.

Ce genre de chose (à la hache):

$etape = $_POST['etape'];
if (! $action) $action = "grille";

switch ($action) {
case "grille" :
print '<form action="truc.php?etape=traiter" method=POST>' print
'<input type=text name=nom size >'; ....
break;
case "traiter" :
$nom = $_POST['nom'];
ajouter_dans_base($nom)
header("Location: truc.php?etape¯fichersucces&nom=$nom") exit;
break;
case "afficher" :
$nom = $_GET['nom'];
print "<h1>Succes</h1> On a bien ajoute $nom"; break;
}
}



Cela ne résoud pas le problème : que se passe-t-il si le /refresh/ est
fait avec $action = "traiter" ? Les solutions proposés dans ce fil sont
justement là pour résoudre ce problème.


--
David JOURAND - http://www.numabilis.com
Supprimer "site." et ".invalid" de mon adresse mail pour me répondre.

Avatar
Michel Billaud
David JOURAND writes:

Le Fri, 15 Sep 2006 17:31:33 +0000, Michel Billaud a écrit :

Scinder ça en deux étapes
* la premiere fait le traitement, et fait une redirection sur la
seconde * la seconde fait uniquement un affichage.

Si l'utilisateur demande à réafficher la page, le traitement n'est pas
relancé.


Cela ne résoud pas le problème : que se passe-t-il si le /refresh/ est
fait avec $action = "traiter" ? Les solutions proposés dans ce fil sont
justement là pour résoudre ce problème.


Les trois solutions sont complémentaires, aucune ne résoud le problème
à elle toute seule.

- la redirection affichage/traitement évite que le navigateur ne renvoie
l'action traiter suite à un [back] malencontreux, ce qui doit couvrir
95% des cas où le problème se pose.
- le jeton connu par la session et present en "hidden" dans la grille
permet effectivement de vérifier que les données reçues viennent bien
du dernier formulaire envoyé par l'application, et pas d'une
page plus ancienne.
- un bon contrôle des opérations sur la base est de toutes façons
indispensable, puisqu'il faut prévoir l'accès concurrent aux données
par plusieurs utilisateurs.

MB
--
Michel BILLAUD
LABRI-Université Bordeaux I tel 05 4000 6922 / 05 5684 5792
351, cours de la Libération http://www.labri.fr/~billaud
33405 Talence (FRANCE)


Avatar
John GALLET
[Dernière intervention de ma part dans ce thread, je fatigue.]

Les trois solutions sont complémentaires, aucune ne résoud le problème
à elle toute seule.


Je proteste, relisez la définition d'une clef primaire. Conseil : lisez
là "à l'envers" (i.e. ce qu'elle implique pour la table/relation et non
la manière de la choisir au niveau d'un rang).

- un bon contrôle des opérations sur la base est de toutes façons
indispensable, puisqu'il faut prévoir l'accès concurrent aux données
par plusieurs utilisateurs.


L'accès concurrentiel entre utilisateurs n'a rien à voir là dedans, même
si c'est un problème à gérer bien entendu.

Si c'est de l'INSERT, il suffit de choisir correctement ses clefs
uniques/primaires. On peut alors soumettre les *mêmes données*
watt-milliarsds de fois, il n'y a *AUCUN* traitement à mettre en place
autre que tester correctement le code d'erreur spécifique au SGBDR (ou
rendu transparent par une couche d'abstraction) et afficher le message
correspondant. Point barre.

Si ce ne sont pas STRICTEMENT les mêmes données dans la partie qui
constitue la clef, que la soumission multiple ait lieu 10 fois en moins
de 30 secondes de temps, ou à 2 ans d'intervalle, on ne saura pas plus
la gérer dans un cas que dans l'autre, on peut seulement essayer
d'éviter le premier cas avec un jeton/variable d'état, mais on n'évitera
JAMAIS le second (automatiquement en tous cas, si un modérateur humain
traite les données, c'est autre chose).

Si c'est de l'UPDATE ou il est atomique et les accès concurrentiels sont
déjà gérés, ou il ne l'est pas et la solution réside dans une
transaction, mais l'unicité du traitement ne posera problème que pour
les modifications de compteurs (style statistiques ou gestion de stock),
et on peut si nécessaire encapsuler le déclenchement de l'action par
rapport à la présence d'une variable d'état dans la session (il y a
selon les cas des solutions plus simples, par exemple dans le cas d'un
site marchand, on vide le panier dans la transaction et le problème du
multi-soumission est résolu, la commande ne peut avoir lieu qu'une fois
et une seule).

Si c'est du DELETE ou du SELECT, on n'a que l'atomicité à gérer, par
transaction si plusieurs requêtes SQL sont incontournables, ou par
jointure (SELECT principalement, certains les SGBDR n'aiment pas trop
les DELETE en jointure même si c'est parfois bien pratique).

a++;
JG

Avatar
Michel Billaud
John GALLET writes:

[Dernière intervention de ma part dans ce thread, je fatigue.]

Les trois solutions sont complémentaires, aucune ne résoud le problème
à elle toute seule.


Je proteste, relisez la définition d'une clef primaire. Conseil : lisez
là "à l'envers" (i.e. ce qu'elle implique pour la table/relation et non
la manière de la choisir au niveau d'un rang).


Ok, si on limite le problème à l'insertion dans une table avec des clés
grantissant l'unicité. Je généralisais aux actions sur la base en général.

- un bon contrôle des opérations sur la base est de toutes façons
indispensable, puisqu'il faut prévoir l'accès concurrent aux données
par plusieurs utilisateurs.


L'accès concurrentiel entre utilisateurs n'a rien à voir là dedans, même
si c'est un problème à gérer bien entendu.


voila.

Si c'est de l'INSERT, il suffit de choisir correctement ses clefs
uniques/primaires.


Quand y en a, et y en a pas toujours.

Contre-exemple, un site genre banque pour rire où il y a des
"virements" à faire d'un compte à un autre. On n'attribue pas un
identifiant a priori à chaque virement, donc si on reposte l'ordre de
virement (emetteur, destinataire, montant), c'est pas la vérification
SQL qui y peut quelque chose... Même remarque pour l'UPDATE.

La solution est alors le jeton pour évite de re-traiter un formulaire.

MB


--
Michel BILLAUD
LABRI-Université Bordeaux I tel 05 4000 6922 / 05 5684 5792
351, cours de la Libération http://www.labri.fr/~billaud
33405 Talence (FRANCE)


Avatar
Larroque
Michel Billaud disait ceci en ce jour du 18.09.2006 16:29:

Si c'est de l'INSERT, il suffit de choisir correctement ses clefs
uniques/primaires.


Quand y en a, et y en a pas toujours.

Contre-exemple, un site genre banque pour rire où il y a des
"virements" à faire d'un compte à un autre. On n'attribue pas un
identifiant a priori à chaque virement, donc si on reposte l'ordre de
virement (emetteur, destinataire, montant), c'est pas la vérification
SQL qui y peut quelque chose... Même remarque pour l'UPDATE.

La solution est alors le jeton pour évite de re-traiter un formulaire.



Il doit toujours y avoir une clé primaire dans une table d'une base de
données de SGBD relationnel.

Dans l'exemple que vous donnez il manque la date de la transaction et
l'ensemble {emetteur, destinataire, date } forme la clé de la table.

--
---- Pierre ----
Moi, lorsque je n'ai rien à dire, je veux qu'on le sache.
Raymond Devos


1 2 3 4