よくあるPHPでの実装を模倣したやり方(非推奨)

ブラウザは再読み込みするとHTTPメソッドのパラメーターもセットで送信してしまうため、フォームの入力画面と完了画面がPOSTメソッドパラメーターの通信だけで完結している場合、完了画面で再読み込みすると同じPOSTパラメーターが送信されてしまう。

そのため、POSTではなく、PHPだと$_SESSIONを使用して、送信完了時にセッションを破棄することでページ遷移の正当性を確認し多重送信を阻止していることが多い。
WordPressの場合も同様に$_SESSIONを使用して実現できるが、下記のようにセッションの始まりは最初に記述する必要がある。
本来であればルーティング時にセッションを使用するかを判断する場合が多いので template_redirect フックで書きたいところだが、それでは session_start 関数が発火しない。
なので、別のスコープで処理を別々に加える必要があり、管理性を損なう。

<?php

// init フックに書かないとセッションを使用できない
add_action('init', function() {
  if ( session_status() !== PHP_SESSION_ACTIVE ){
    session_start();
  }
});

// セッションの具体的な値や処理を記述
add_action('template_redirect', function() {
  $_SESSION['hoge'] = '1';
  session_write_close();
  // 以降ルーティング処理
  wp_redirect(home_url('~'));
  exit;
});

またプラグインやテーマで他にセッションを使用している場合はその互換性を考えプログラムをしなければいけない。
またセッションを破棄する時には下記のように個別に記述する必要がある。

// テンプレート側の処理
<h1><?php echo $_SESSION['hoge']; ?></h1>
<?php

// 他にセッションが使われている可能性があるから全消去はできない
// $_SESSION = [];
// 個別にセッションを削除する必要がある
unset($_SESSION['hoge']);

このやり方の場合、書く側も読む側も難解となること間違いなしのため、おすすめしかねる。

WordPressの特性を活かしたやり方

そこでWordPressのエコシステムを使ったより良い方法があるので紹介しよう。
それは Transient API を使ったやり方だ。

// 一時的に保持したいデータを記述
add_action('template_redirect', function() {
  set_transient('hoge', 'success', 10);
  // 多人数が使用するフォームの場合は、同じ名前空間を使用すると衝突する可能性があるため、フォーム側でユニークな値を付与して使用するか、ログインユーザー限定の場合はIDを使用したりするとよい
  // set_transient('hoge' . $_POST['unique_id'], 'success', 10);

  // 以降ルーティング処理
  wp_redirect(home_url('~'));
  exit;
});
// テンプレート側の処理
<h1><?php echo get_transient('hoge'); delete_transient('hoge'); ?></h1>

Transient API はデータベースを使用し、データを保存するAPIだ。
WordPressには他にも同じような Options API があるが、これとの違いは Transient API は set_transient 関数の第三引数で有効期限の設定ができることだ。
上のサンプルでは 10 と設定しているので、10秒間のみ値を保持する(ただ、テンプレート側の処理でデータを破棄しているため、別に設定をしなくてもいいし、設定することでスケジューリングの関数も動いてしまうからむしろない方が望ましいように思えてきた)。

データベースに保存する分処理が重くなってしまうが、WordPressだからこそできるやりかた(データベースを用意する手間を考えると自前のシステムではなかなか実装しないだろう)である。

補足:データの処理方法について

さきほどの template_redirect フックを使用した場合、データの処理の記述方法について効率的なやり方があるのでご紹介。

データ送信成功時の場合、wp_redirect関数を使用することでリダイレクトを行うことでリロードの対策をできるが、エラー処理で対策を行わなくていいのであれば、PHPの include 制御構文を用いて同じスコープで処理を行うとエラーコードの引き渡しがなくなるため、効率的にコーディング可能だ。

// 一時的に保持したいデータを記述
add_action('template_redirect', function() {
  $errors = [];
  // 何かエラー処理
  if ($hoge === '') {
    $errors[] = 'hoge-empty';
  }
  // 何かエラー処理

  if (empty($errors)) {
    // エラーがないのでリダイレクト
    wp_redirect(home_url('~'));
    exit;
  } else {
    // エラーがある場合は
    // include 制御構文を使用することで、hoge.php では $errors などの変数と同じスコープになるため、変数を参照できるため、パラメーター引き渡しなどの処理を省くことができる
    include dirname(__FILE__) . '/templates/hoge.php';
    exit;
  }
});

今回は珍しく懇切丁寧に解説してみたので、書くの疲れた。。