shigechi-64's diary

自由・自主・自立・自尊

ブラウザの更新ボタンとPOSTによる処理を区別したい

tableをPHPスクリプトで生成するとします。 

各行のデータはCSVで別ファイルに格納されており、ページを表示する際にそれを読み込んできて、1レコードにつき1行を生成していきます。各行の最後にはチェックボックスが付加され、削除ボタンを押すことでチェックした行を削除することができるものとします。tableは以下のようなイメージです。

 column1column2column3 
record1hogepiyofoo
record2hogepiyofoo
record3hogepiyofoo

各行のcheckboxには生成の際に連番が付与されています。削除ボタンが押されたときはその連番をスクリプト自身にPOSTし、スクリプト内の削除処理に渡します。それに基づき削除処理は該当レコードを元のCSVから削除します。その後再描画されたtableでは削除されたレコードが消えているということになります。

連番は上から順番に0、1、2・・・という具合に付与されています。例えば上記のrecord2に付与された連番は1です。record2にチェックを入れて削除ボタンを押した場合、削除処理には「連番1のレコードを削除してくれ」と処理を依頼することになります。処理後のtableは以下のようになりますね。

 column1column2column3 
record1hogepiyofoo
record3hogepiyofoo

この状態になるとrecord1が連番0、record3が連番1ということになります。

ところで、ブラウザには更新ボタンがあります。ブラウザの更新ボタンを押した場合、直前に行ったHTTPメソッドが再度実行されます。あるページをGETで取得していた場合、更新ボタンを押すともう一度そのページがGETメソッドで取得されます。これはPOSTでも同じで、あるページでPOSTメソッドを実行した場合、更新ボタンを押すと再度同じ内容のPOSTメソッドが実行されます。当然リクエストボディのデータも送信されることになります。

ここで2番目に表示したtableに目を向けます。前述のとおり真ん中のrecord2が削除されているのでrecord1が連番0、record3が連番1になっています。この状態でブラウザの更新ボタンを押すとどうなるでしょうか。

そう、先ほどのPOSTで行った「連番1のレコードを削除してくれ」という処理が再度行われることになります。レコードにチェックもしていないし、削除ボタンも押していないのに連番1をもつrecord3が削除されてしまいます。これは挙動として非常に問題があります。

この問題を回避するために、header関数を使用してみました。header関数は生のHTTPヘッダを送信するための関数です。
PHP: header - Manual
この関数による自分自身へのリダイレクトを処理の最後に挿入します。

if( $_POST["delete"] ){
  //処理
  ・
  ・
  ・
  header("Location: {$_SERVER['PHP_SELF']}");
  exit;
}

簡単にいうと「POSTされてきた時に元データへの処理はするが、最終的にはGETを行う」という感じでしょうか。これによって更新ボタンが押されたときもそのページをGETしてくるだけなので、前述のような問題は起こりません。削除ボタンが押された場合はもちろん処理が行われたあと、自分自身を表示するので、この例でいうとちゃんと削除したい行が消えている、ということになります。

注意点としてはマニュアルにも記載がありますが、header関数より前に何らかの出力をしているとエラーになります。デバッグのためのechoなんかも当然引っかかってしまいますので注意が必要です。

と、ここまで書いてきて何なんですが、いまいちこのheader関数の使い方がよくわかりません。一応目的は達成できてるんですが、ほんとにこういう使い方なのか?と思わないでもないし、そもそも問題となっている件に関してももっといいやり方があるような気がしてなりません。が、とりあえずうまく動いているようなのでまあいいや、ということにしておきます。