YouTube "Route 87" | 35歳からオーストラリアのシドニーでシェフを目指す男の日常をお届け。

【PHP】$_POSTと$_SESSIONを使ってフォームの値を送信する方法を解説




$_POSTと$_SESSIONを使用して値を送る仕組み

今回用意された問題は、「ロジック」と「ビュー(HTML)」を別ファイル管理しています。実際の現場でもこのスタイルで仕事を進めていくことが多いそうです。

PHPはHTML内にプログラミングを書き込める利便性がある反面、ソースコードが複雑になりがちです。しかし、ファイルの読み込みを使用することでデザインとプログラムを分離することができます。

「ロジック」と「ビュー(HTML)」を別ファイル管理するメリットは、「ロジック担当者(プログラマー)」と「ビュー担当者(コーダー)」で作業内容が違うので、両者とも自分が担当している作業を分業しながらWebシステムを作成することができます。

デメリットは、画面に表示する値(変数)を「ロジック担当者」と「ビュー担当者」で共有しておかなければなりません。

  • ロジック担当者(プログラマー):ロジック部分作成
  • ビュー担当者(コーダー):各パーツをコーディング

ページ遷移図

この遷移図は理解しているとすごくわかりやすいのですが、理解できてないとチンプンカンプンだと思います。

この遷移図は、ユーザ入力画面からユーザ情報をフォーム($_POST)と$_SESSIONで値を受け渡し、DB(データベース)に登録するまでの流れを表しています。

ポイント
  • $_POSTと$_SESSIONの使い所
  • $_SESSIONの動向

functions.php

<?php
function h($val)
{
  return htmlspecialchars($val, ENT_QUOTES, 'UTF-8');
}
function v($val)
{
  echo '<pre>';
  var_dump($val);
  echo '</pre>';
}

functions.phpによく使う関数を共通関数として登録しておく。

h関数:サニタイジング用関数を定義

ユーザーが入力した値を使用する場合は注意(セキュリティ対策)が必要です。なぜならば、悪意があるユーザーが悪意ある値を入力してくる場合があるからです。不正なJavaScriptコードを使用して他のサイト訪問者に攻撃を仕掛けることをクロスサイトスクリプティング(XSS)と呼びます。

こういったトラブルを防ぐには、htmlspecialchars()関数でHTMLで意味をもつ記号をエスケープします。

htmlspecialchars()関数:HTMLタグを無効化する関数

第1引数($val):string変換対象文字列
第2引数(ENT_QUOTES):ENT_QUOTES→シングルクォートとダブルクォートを共に変換
第3引数(‘UTF-8’):stringエンコード

htmlspecialchars()関数を実行すると、HTMLで意味を持つ記号を実体参照に置き換えてくれます。(ex. 「<」 → 「&lt;」,「&」→「&amp;」)

このようなエスケープ操作のことをサニタイジングや無害化と呼びます。

v関数:デバッグ用関数を定義

デバック時に使用するvar_dump()関数をpreタグで囲んだ関数を定義しておく。preタグで囲むことでvar_dump()関数を表示した際に見えやすくしている。

form1.php

<?php
session_start();
session_regenerate_id(TRUE);

require_once __DIR__ . '/functions.php';


v($_SESSION);
$name    = isset($_SESSION['name'])    ? $_SESSION['name']    : NULL;
$subject = isset($_SESSION['subject']) ? $_SESSION['subject'] : NULL;
$body    = isset($_SESSION['body'])    ? $_SESSION['body']    : NULL;

require 'form1_view.php';

session_start()関数:セッション変数利用開始する関数

session_start()関数を呼び出すことで、サーバ側に値を保存するセッション変数「$_SESSION」を使用できるようになります。他のページと値を共有したいときに使用します。

セッションは、クッキーを使ってユーザを認識し必要なデータをサーバ側に保存して使用します。データがサーバ側に保存されるため、悪意あるユーザにデータを改ざんされる心配がありません。クッキーに比べて安全性が高く、データ量の制限も実質ありません。

session_regenerate_id()関数:セッションIDを再発行する関数

セッションハイジャック対策でPHPSESSIDの値を変更する。引数は常に「TRUE」を指定し、古いセッションファイルを削除する。ログイン時やセッション開始ページ、一定時間経過したときにセッションIDを新たに設定することで、セッションハイジャックの危険性を減らすことができます。

複数ページで処理する場合は、始点となるファイルでPHPSESSIDの値を変更することが多い。

require_once DIR . ‘/functions.php’:共通関数の読み込み

functions.phpに登録した共通関数のh関数とv関数を使用できるように、共通関数ファイルを読み込む。

三項方程式

今回ビューファイルで必要な値「名前」「件名」「本文」をロジックで用意している。

$name = isset($_SESSION[‘name’]) ? $_SESSION[‘name’] : NULL;

三項方程式を使用し、 isset($_SESSION[‘name’])は「$_SESSION[‘name’]」が存在するか調べます。初回アクセス時はまだ「$_SESSION[‘name’]」は存在しないので、変数にはfalseの「NULL」を代入します。

require ‘form1_view.php’;

reqiuireを使ってパーツ化したコンテンツを読み込みます。

form1.phpでは「form1_view.php」ファイルを読み込みます。

form1_view.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>入力画面|フォーム</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div class="header">
    <div class="inner">
      <h1>入力画面</h1>
    </div>
  </div>
  <div class="content">
    <div class="inner">
      <?php if (isset($error)) : ?>
      <div class="error">
        <ul>
          <?php foreach ($error as $val) : ?>
          <li><?php echo $val; ?></li>
          <?php endforeach; ?>
        </ul>
      </div>
      <?php endif; ?>

      <form action="form2.php" method="post">
        <dl>
          <dt>名前</dt>
          <dd>
            <input type="text" name="name" value="<?php echo h($name); ?>" size="50">
          </dd>
          <dt>件名</dt>
          <dd>
            <input type="text" name="subject" value="<?php echo h($subject); ?>" size="50">
          </dd>
          <dt>内容</dt>
          <dd>
            <textarea name="body" cols="50" rows="10"><?php echo h($body); ?></textarea>
          </dd>
        </dl>
        <p>
          <input class="btn" type="submit" value="確認画面へ">
        </p>
      </form>
    </div>
  </div>
  <div class="footer">
    <div class="inner">
      <p class="copyright">© フォーム</p>
    </div>
  </div>
</body>
</html>
      <?php if (isset($error)) : ?>
      <div class="error">
        <ul>
          <?php foreach ($error as $val) : ?>
          <li><?php echo $val; ?></li>
          <?php endforeach; ?>
        </ul>
      </div>
      <?php endif; ?>

form1.phpでは$errorが存在しないので、エラー表示部分の利用はありません。
しかし、後々出てくる「form2.php」で活躍します。

      <form action="form2.php" method="post">
        <dl>
          <dt>名前</dt>
          <dd>
            <input type="text" name="name" value="<?php echo h($name); ?>" size="50">
          </dd>
          <dt>件名</dt>
          <dd>
            <input type="text" name="subject" value="<?php echo h($subject); ?>" size="50">
          </dd>
          <dt>内容</dt>
          <dd>
            <textarea name="body" cols="50" rows="10"><?php echo h($body); ?></textarea>
          </dd>
        </dl>
        <p>
          <input class="btn" type="submit" value="確認画面へ">
        </p>
      </form>

HTMLでフォームを作成します。
今回は「form2.php」にPOST形式で値を送るので、action=”form2.php” method=”POST”となります。

ユーザから情報を得る場合は、基本$_POSTを使用して値を取得します。

inputタグのvalueの部分がユーザ入力値としてform2.phpに送られるので、ユーザ入力値の部分をh関数のhtmlspecialchars()関数で入力値をサニタイジングします。

v($_SESSION);

ここでセッションをv()関数を使って確認します。

form1.php
form1.php

最初のform1.phpではまだ$_SESSIONに値が入ってないことが確認できます。

form2.php

form1.phpで確認画面へボタンをクリックするとform2.phpへ移ります。

<?php
session_start();
require_once __DIR__ . '/functions.php';

v($_POST);

$name    = isset($_POST['name'])    ? $_POST['name']    : NULL;
$subject = isset($_POST['subject']) ? $_POST['subject'] : NULL;
$body    = isset($_POST['body'])    ? $_POST['body']    : NULL;

$name    = trim($name);
$subject = trim($subject);
$body    = trim($body);

$error = [];

if ($name == '') {
  $error[] = 'お名前は必須項目です。';
} else if (mb_strlen($name) > 20) {
  $error[] = 'お名前は20文字以内でお願い致します。';
}

if ($subject == '') {
  $error[] = '件名は必須項目です。';
} else if (mb_strlen($subject) > 50) {
  $error[] = '件名は50文字以内でお願い致します。';
}

if ($body == '') {
  $error[] = '内容は必須項目です。';
} else if (mb_strlen($body) > 500) {
  $error[] = '内容は500文字以内でお願い致します。';
}

if (count($error) > 0) {
  require 'form1_view.php';
} else {
  $_SESSION['name']    = $name;
  $_SESSION['subject'] = $subject;
  $_SESSION['body']    = $body;
  require 'form2_view.php';
}

form2.phpではform1.phpから$_POSTで送った値を取得します。

form2.php
form2.php

form1.phpから$_POSTで送られた値はform2.phpに届いていることが確認できますが、この時点でも$_SESSIONにはまだ値がありません。

$name    = isset($_POST['name'])    ? $_POST['name']    : NULL;
$subject = isset($_POST['subject']) ? $_POST['subject'] : NULL;
$body    = isset($_POST['body'])    ? $_POST['body']    : NULL;

$name    = trim($name);
$subject = trim($subject);
$body    = trim($body);

三項方程式でそれぞれ変数にform1.phpから$_POSTで送られた値を代入します。
さらにtrim()関数で先頭・末尾にあるホワイトスペースを削除したものを変数へ再代入します。

$error = [];

if ($name == '') {
  $error[] = 'お名前は必須項目です。';
} else if (mb_strlen($name) > 20) {
  $error[] = 'お名前は20文字以内でお願い致します。';
}

if ($subject == '') {
  $error[] = '件名は必須項目です。';
} else if (mb_strlen($subject) > 50) {
  $error[] = '件名は50文字以内でお願い致します。';
}

if ($body == '') {
  $error[] = '内容は必須項目です。';
} else if (mb_strlen($body) > 500) {
  $error[] = '内容は500文字以内でお願い致します。';
}

$error = [];
エラー変数を配列化して、エラー時に出力するエラー文を配列管理します。

mb_strlen()関数で変数値の文字列の長さを取得します。
if分を使い、未入力時と文字数を超えた場合にエラー変数にエラー文を配列で管理していきます。

if (count($error) > 0) {
  require 'form1_view.php';
} else {
  $_SESSION['name']    = $name;
  $_SESSION['subject'] = $subject;
  $_SESSION['body']    = $body;
  require 'form2_view.php';
}

エラーがある場合は、「fomr1_view.php」を読み込みます。

エラーがない場合は、$_SESSIONに確定したユーザ入力値を代入し、他のページでも使えるようにします。そして「form2_view.php」を読み込みます。

form2_view.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>確認画面|フォーム</title>
  <link rel="stylesheet" href="css/style.css">
</head>

<body>
  <div class="header">
    <div class="inner">
      <h1>確認画面</h1>
    </div>
  </div>
  <div class="content">
    <div class="inner">

      <p>以下の内容でよろしければ送信ボタンを押してください。</p>

      <dl class="confirm">
        <dt>お名前:</dt>
        <dd><?php echo h($name); ?></dd>
        <dt>件名:</dt>
        <dd><?php echo h($subject); ?></dd>
        <dt>内容:</dt>
        <dd><?php echo nl2br(h($body)); ?></dd>
      </dl>

      <div class="confirm-btn">
        <p><a class="btn" href="form1.php">入力画面に戻る</a></p>
        <p><a class="btn" href="form3.php">送信する</a></p>
      </div>
    </div>
  </div>
  <div class="footer">
    <div class="inner">
      <p class="copyright">© フォーム</p>
    </div>
  </div>
</body>
</html>
      <dl class="confirm">
        <dt>お名前:</dt>
        <dd><?php echo h($name); ?></dd>
        <dt>件名:</dt>
        <dd><?php echo h($subject); ?></dd>
        <dt>内容:</dt>
        <dd><?php echo nl2br(h($body)); ?></dd>
      </dl>

ユーザ入力値を使用するので、h()関数を使ってエスケープします。

記述量が多い「内容」では「nl2br()」を使って見やすく表示します。

v($_SESSION);

ここでもセッションをv()関数を使って確認します。

form2.php
form2.php

上記で記述したように、form1.phpからページ移動してきたので$_POSTには値が入っていますが、$_SESSIONにはまだ値がありません。

form1.php
form1.php

その後「入力画面へ戻る」ボタンをクリックしform1.phpのページへ戻ると、最初に値が入ってなかった$_SESSIONに値が入っています。

これはform2.phpでエラーがなかった場合に$_SESSIONに値が代入されるからです。

form2.php
form2.php

再びform2.phpへ戻るとこちらの$_SESSIONにもちゃんと値が入っていることが確認できます。

form3.php

<?php
session_start();
require_once __DIR__ . '/functions.php';

$name    = isset($_SESSION['name'])    ? $_SESSION['name']    : NULL;
$subject = isset($_SESSION['subject']) ? $_SESSION['subject'] : NULL;
$body    = isset($_SESSION['body'])    ? $_SESSION['body']    : NULL;

unset($_SESSION['name']);
unset($_SESSION['subject']);
unset($_SESSION['body']);

$error = [];
if ($name === NULL) {
  $error[] = '名前の値なし';
}

if ($subject === NULL) {
  $error[] = '件名の値なし';
}

if ($body === NULL) {
  $error[] = '本文の値なし';
}

受け取った値を使った処理:例)データベースへの登録など

if (count($error) > 0) {
  require 'error_view.php';
} else {
  require 'form3_view.php';
}

form1.phpからform2.phpへ値を送信するときは、$_POSTを使用しましたが、form2.phpからform3.phpへの値の受け渡しは$_SESSIONを使用します。

$name    = isset($_SESSION['name'])    ? $_SESSION['name']    : NULL;
$subject = isset($_SESSION['subject']) ? $_SESSION['subject'] : NULL;
$body    = isset($_SESSION['body'])    ? $_SESSION['body']    : NULL;

$_SESSIONから値を取得し、それぞれ変数に値を代入したら$_SESSIONを削除します。

unset($_SESSION['name']);
unset($_SESSION['subject']);
unset($_SESSION['body']);

unset()関数:指定した変数の割り当てを解除・配列内の部屋を削除します。セッション変数の部屋は明示的に削除しないとずっと存在してしまうので、使用しなくなった部屋はunset()関数で削除しなければなりません。

$error = [];
if ($name === NULL) {
  $error[] = '名前の値なし';
}

if ($subject === NULL) {
  $error[] = '件名の値なし';
}

if ($body === NULL) {
  $error[] = '本文の値なし';
}

受け取った値を使った処理:例)データベースへの登録など

if (count($error) > 0) {
  require 'error_view.php';
} else {
  require 'form3_view.php';
}

エラーがあれば、エラー変数にエラーメッセージを代入します。

エラーがあるとき、「error_view.php」を読み込みます。
エラーがないときは、「form3_view.php」を読み込みます。

実際にデータベースに値を受け渡すには、この時点でロジックを組んでいくみたいです。

v($_SESSION);

ここでもセッションをv()関数を使って確認します。

form3.php
form3.php

unset($_SESSION)の前後でv()関数で$_SESSIONの動向を確認します。

unset前では$_SESSIONに値が入っているのに対し、unset後の$_SESSIONには値が入っていないことが確認できます。

form3_view.php

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>完了画面|フォーム</title>
  <link rel="stylesheet" href="css/style.css">
</head>

<body>
  <div class="header">
    <div class="inner">
      <h1>完了画面</h1>
    </div>
  </div>
  <div class="content">
    <div class="inner">

      <p>以下の値で処理をしました。</p>

      <dl class="confirm">
        <dt>お名前:</dt>
        <dd><?php echo h($name); ?></dd>
        <dt>件名:</dt>
        <dd><?php echo h($subject); ?></dd>
        <dt>内容:</dt>
        <dd><?php echo nl2br(h($body)); ?></dd>
      </dl>

      <div class="content-footer">
        <p><a href="form1.php">入力画面に戻る</a></p>
      </div>
    </div>
  </div>
  <div class="footer">
    <div class="inner">
      <p class="copyright">© フォーム</p>
    </div>
  </div>
</body>

</html>

ユーザ入力値を表示する際は毎回h()関数でエスケープしておきます。

error_view.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>エラー|フォーム</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div class="header">
    <div class="inner">
      <h1>エラー</h1>
    </div>
  </div>
  <div class="content">
    <div class="inner">

      <?php /* ?>
      <?php if (isset($error)) : ?>
      <div class="error">
        <ul>
          <?php foreach ($error as $val) : ?>
          <li><?php echo $val; ?></li>
          <?php endforeach; ?>
        </ul>
      </div>
      <?php endif; ?>
      <?php */ ?>

      <p>セッションが切れました</p>
      <p><a href="form1.php">もう一度やり直してください。</a></p>
    </div>
  </div>
  <div class="footer">
    <div class="inner">
      <p class="copyright">© フォーム</p>
    </div>
  </div>
</body>
</html>
      <?php /* ?>
      <?php if (isset($error)) : ?>
      <div class="error">
        <ul>
          <?php foreach ($error as $val) : ?>
          <li><?php echo $val; ?></li>
          <?php endforeach; ?>
        </ul>
      </div>
      <?php endif; ?>
      <?php */ ?>

この部分はデバック用なので実際には表示されません。
エラーが出たときにどんなエラーが出たのか確認できます。