Webページにおけるページ間状態管理まとめ

状態管理の方法は、大きく2つにわけることができます。
一つはクライアントサイドでの状態管理、もう一つはサーバーサイドでの状態管理です。

クライアントサイドでのWeb ページ間の状態管理

クライアントサイドで状態管理を完結させるもので、一時的なデータ共有の場合は、この方法をとるのが適切です。
先に例示したユーザーの入力した内容を確認したり、元に戻ったときに復元するというような処理は、この方法によるべきでしょう。
クライアントサイドでの管理というのは、要するにブラウザが用意したキャッシュシステムを活用するものです。
具体的には次の3つの手法があります。

  • LocalStorage: 使用例としては、ユーザーのテーマ設定(ダークモード)を保存。
  • SessionStorage: 使用例としてはセッション単位のフォーム入力内容を保持。
  • Cookie: 使用例としてはサーバーとのやり取りに必要なセッション ID を保存。

localStorageによる処理

具体的なコードを示します。
このコードはWebページに設置されたチェックボックスの値を保持しておくためのコードです。

  • localStorageに値を保存
// リンクがクリックされたときにデータを保存
document.querySelectorAll(".tablenav-pages a").forEach(function(link) {
  link.addEventListener("click", function(event) {
    localStorage.setItem(storageKey, JSON.stringify(selectedPosts));・・・①
  });
});
  • localStorageから値を読みだし(DOMContentLoadedイベントで呼び出すといいでしょう。)
// チェックボックスの状態を復元
function restoreSelectedPosts() {
  selectedPosts = JSON.parse(localStorage.getItem(storageKey)) || [];・・・②

  document.querySelectorAll("input[name='export_posts[]']").forEach(function(checkbox) {
    if (selectedPosts.includes(checkbox.value)) {
      checkbox.checked = true;
    }
  });
  
}

①と②がlocalStroageを使用している部分です。メソッドとしてはsetItemとgetItemがあり、setItemではキーと値を引数で渡し、getItemでは値を引数で渡します。オブジェクトや配列はJSON形式に変換するということを忘れないようにしましょう。

sessionStorageによる処理

こちらもlocalStorageと使い方は同じです。
localStorageとsessionStorageの違いは、localStrorageが永続的にデータを保存するのに対し、sessionStorageはブラウザタブが閉じられるまでの一時的なデータ保存というところです。
より具体的に言うと、localStrorageに保存したデータはブラウザを終了して、再起動しても保存されたデータが再現されるのに対し、sessionStorageはブラウザを終了したり、ブラウザタブを消去するとデータは再現されないのです。

cookieによる処理

cookieも、localStorageやsessionStorageと同じく、クライアントサイドにデータを保存する仕組みなのですが、サーバーとの通信に適したセキュリティ機能を持っていることから、クライアントサイドに保存されたデータと、サーバーサイドに保存されたデータの照合を行うために活用されるます。これはCookie の自動送信という仕組みで実現されます。
いわば、クライアントサイドとサーバーサイドをつなぐ情報の橋渡し役なのです。
これに対して、localStorageやsessionStorageという仕組みはサーバーサイドに自動的にデータを送るという機能は持っていません。

では、 Cookie の自動送信機能の流れを見てみましょう。
1️. クライアントがサーバーに初回リクエストを送信

  • クライアントが初めてサーバーにアクセスします(例: ユーザーが Web サイトを開く)。
  • この段階ではCookie がまだ設定されていないため、 Cookie に関するヘッダー情報は含まれていません。
  1. ユーザーがWebサイトでユーザーIDの登録などを行なう。
  • サーバーサイドにその情報が保存されます。
  • サーバーはセッション ID や認証トークン を Set-Cookie ヘッダーを使ってクライアントに送信します。
  • クライアントサイドでは、これをcookieとして保存します。
  1. それ以降にWebサイトにクライアントがアクセスする。
  • クライアントが Cookie を保存した後、次回以降のリクエストには 自動的に Cookie が付与されます。
  • サーバーはリクエスト内の Cookie ヘッダーを確認し、対応するセッション情報を復元します。
  1. サーバーが再度 Set-Cookie を送信する場合
  • Cookie を更新する必要がある場合
  • 新しい Cookie を追加する場合

サーバーサイドでのWeb ページ間の状態管理

Webページの状態管理はECサイトなどでは、異なるデバイスやブラウザでも状態を再現する必要が出てきます。このような場合はクライアントサイドの仕組みでは対応できず、サーバーサイドでページ間の状態管理が必要となります。
また、セキュリティ上の問題でクライアントに情報を残すことが問題である場合も多いでしょう。クレジットカードの情報をあちこちのクライアントに残すと、どんなところで漏洩してしまうかわかりません。

サーバーサイドの状態管理はいくつかの方法がありますが、今回は次の3つについて解説します。

  1. PHPの$_SESSIONによる状態管理
  2. データベースに情報を保存する状態管理
  3. Hiddenフィールドによる状態管理

PHPの$_SESSIONによる状態管理

PHPにはグローバル変数である$_SESSIONを使用して状態管理を行うというのが一般的です。
具体的なコードは次の様になります。

session_start(); // セッション開始

// データ保存
$_SESSION['user_id'] = 123;

// データ取得
echo $_SESSION['user_id'];

ただし、注意しないといけないのは、セッションを開始するためのsession_start();は、HTTP レスポンスのヘッダー情報がクライアントに送信される前に実行しなければなりません。
そうしないと、
Warning: session_start(): Cannot start session when headers already sen
というエラーが発生してしまいます。
したがって、PHPスクリプトの冒頭にコーディングすることになるのですが、WordPressを使っていると、様々なフックで関数が実行されているので、どれが一番最初かよくわからなくなります。
それで解決策としてよく紹介されているのがinitアクションフックで実行するというものです。
しかし、実はこの方法はWordPressでは推奨されておらず、公式プラグインの審査では却下されてしまいます。
理由としては、パフォーマンスに悪影響をあたえるとか、セキュリティ上の問題があるなどといいますが、最大の理由はWordPressが複数のベンダーが提供するプラグインやテーマを使って運用するのが前提になっているからです。
どういうことかというと、session_start();はWebページ一つにつき、一度しか実行することができません。単一のベンダーがWebページを管理している状態なら、そのルールを守ることができるかもしれませんが、複数のベンダーが競合すると、どこで実行されるか保証できないのです。
実際、私も$_SESSIONをつかって、他のベンダーが提供するプラグイン上でエラーを発生させてしまったことがあります。
これは、かなりの問題です。
したがって、WordPress環境では$_SESSIONは使わず、次に説明するデータベースにデータを保存することによる状態管理を採用するようにしましょう。

データベースに情報を保存する状態管理

WordPressのように、サーバーサイドでデータベースが用意されていることが前提になっているのであれば、そのデータベースに状態を保存し、ページ間で状態を共有するのが簡便で堅牢です。セッションに依存せず、永続的な保存が可能です。
WordPressでは、状態管理には wp_optionswp_usermeta を利用します。
特にwp_usermetaを使用すれば、ログインユーザーごとの状態管理が可能で、他のユーザの入力データと入り混じるようなことはありません。
具体的なコードは次の様になります。

function sl_trans_set_flg($flg)
{
  // ユーザーごとに開始フラグを保存(get_current_user_id() を使う)
  update_user_meta(get_current_user_id(), 'sl_trans_newPost', $flg);
}

function sl_trans_get_flg()
{
  $current_user_id = get_current_user_id(); // 現在ログインしているユーザーのIDを取得
  $ret_val = get_user_meta($current_user_id, 'sl_trans_newPost', true);
  return $ret_val;
}

上記の例では、sl_trans_set_flgでデータベースに書き込み、sl_trans_get_flgで読み出します。
特にsession_startのような事前準備も不要です。

Hiddenフィールドによる状態管理

最後にHiddenフィールドによる状態管理を説明しておきます。
Hidden フィールド は、HTML フォームの一部として送信される 非表示のデータ を使って、サーバーサイドで状態を管理する方法です。この手法では、状態を隠しフィールドに埋め込み、フォーム送信時にサーバーへ渡すことで状態を維持します。
具体的には次のようなコードで実行します。
ステップ1

<form method="post" action="address_info.php">
    <label for="name">名前</label>
    <input type="text" id="name" name="name" required>
    <label for="email">メールアドレス</label>
    <input type="email" id="email" name="email" required>
    <button type="submit">次へ</button>
</form>

ステップ 2 (address_info.php)

<?php
// ステップ1のデータを受け取る
$name = $_POST['name'];
$email = $_POST['email'];
?>
<form method="post" action="payment_info.php">
    <input type="hidden" name="name" value="<?php echo htmlspecialchars($name); ?>">
    <input type="hidden" name="email" value="<?php echo htmlspecialchars($email); ?>">
    <label for="address">住所</label>
    <input type="text" id="address" name="address" required>
    <label for="phone">電話番号</label>
    <input type="text" id="phone" name="phone" required>
    <button type="submit">次へ</button>
</form>

ステップ 3 (payment_info.php)

<?php
// ステップ1・2のデータを受け取る
$name = $_POST['name'];
$email = $_POST['email'];
$address = $_POST['address'];
$phone = $_POST['phone'];
?>
<form method="post" action="complete.php">
    <input type="hidden" name="name" value="<?php echo htmlspecialchars($name); ?>">
    <input type="hidden" name="email" value="<?php echo htmlspecialchars($email); ?>">
    <input type="hidden" name="address" value="<?php echo htmlspecialchars($address); ?>">
    <input type="hidden" name="phone" value="<?php echo htmlspecialchars($phone); ?>">
    <label for="credit_card">クレジットカード番号</label>
    <input type="text" id="credit_card" name="credit_card" required>
    <button type="submit">登録する</button>
</form>

この例は、必要な情報を一度にサーバーに送るのではなく、ステップ1、2、3の各ステップで小分けにして順次サーバーに送信しています。
一度にたくさんのデータをユーザーに入力させるようなインターフェースはユーザーの入力意欲を阻害するために使用されることが多いようです。

この例の着目点は以下の部分です。
ステップ2では、サーバーに送信されたデータを、

$name = $_POST['name'];
$email = $_POST['email'];

で受け取り、

<input type="hidden" name="name" value="<?php echo htmlspecialchars($name); ?>">
<input type="hidden" name="email" value="<?php echo htmlspecialchars($email); ?>">

で隠れたinput要素に詰め込んで、再びサーバーに送っているのです。
そしてステップ3でも同様のことを行っています。
つまり、別のページに遷移しても、サーバーからデータを受け取ることで、状態管理ができるというわけです。
ここで誤解しやすいのは既にサーバーにnameやemailのデータを送っているのだから、ステップ2やステップ3でもう一度送る必要はないだろうと考えることです。
しかし、それは間違いです。
POST メソッドでサーバーに送信されたデータ は、リクエストの一部として一時的に存在しています。これは、セッションやデータベースに保存されるデータとは異なり、サーバーはそのリクエストを処理する際にのみそのデータを利用します。

HTTP はステートレスプロトコルであるため、サーバーは各リクエストを独立して処理します。リクエストが完了すると、そのリクエストのデータは破棄され、サーバーは特別な保存処理を行わない限り前回のリクエストに関する情報を保持しません。

まとめ

このように、ページ間の状態管理はクライアントサイドで完結させるのが適切なものと、サーバーサイドで管理するのが適切なものがあります。
これらの使い分けは非常に重要ですので、この機会に理解を整理していただけたら幸いです。

コメント

“Webページにおけるページ間状態管理まとめ” への4件のフィードバック

  1. itmaroonのアバター
    itmaroon

    コメントを入れます

    1. itmaroonのアバター
      itmaroon

      返信はここからでした

  2. itmaroonのアバター
    itmaroon

    もう一つ入れます

  3. itmaroonのアバター
    itmaroon

    このコメントに対する返信です

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です