セッションとは
セッションはデータを一時的に保存する
セッション(session) は、ブラウザやスマホで入力したデータを、サーバー上にデータを一時的に保存する仕組みで、Webシステムでは必須な処理です。例えばログイン認証後に、他のページでもログイン状態を保持したいときに利用します。
セッションとデータベースの違い
Webシステムのデータの保存は大きく2つにわかれます。
- 永続的にデータ保存(テキスト、データベース)
- 一時的に保存(セッション、インメモリ)
データベースでログイン状態を管理すると、大量のユーザアクセスごとにデータベースに問い合わせるため、サーバ負荷の原因になってしまいます。 よって、Webシステムではログイン状態をセッションやメモリに保存することが多くあります。
セッションとCookie(クッキー)の違い
Cookie(クッキー) は、セッションと同じように状態をデータ保存しますが、セッションとCookie では保存される場所が違います。
- セッション:サーバーに保存
- Cookie:ブラウザに保存
Cookie の問題点
Cookie はブラウザにデータ保存するので、データが書き換える可能性が高くなり、情報の信頼性が比較的低い傾向にあります。 セッションはサーバにデータ保存するため、セッションデータ自体は Cookieより安全といえます。
ステートレスとステートフル
ステートレス(stateless)
セッションや Cookie などに状態を保存していないことをステートレス(stateless) といいます。
例えば、ショッピングサイトで商品をカートに入れたが、状態を保存せずに他のページにアクセスすると、当然ながらカート商品を表示できません。
ステートフル(stateful)
セッションやCookieなどにデータを一時保存し、他のページで利用できる状態を ステートフル(stateful) といいます。ステートフルだと他のページに移動して商品データ表示することができます。
セッションの基本
セッション管理
セッションはどのクライアント(ブラウザ)が接続しているか判別するために、セッションIDを生成して管理します。
DevToolsでセッションID確認
「セッションID」はブラウザのCookieに保存され、DevToolsなどで確認できます。
セッションの開始
セッションデータを利用する前に、まずセッションを開始します。
session_start()
$_SESSION
$_SESSION はセッションを保存するスーパーグローバル変数です。連想配列になっていてキーを指定してデータの登録や取得ができます。
$_SESSIONの値を設定
$_SESSION['キー'] = 値;
$_SESSIONの値を取得
$_SESSION['キー'];
カウンターページ
ページにアクセスするたびに、カウンターを1増加してみましょう。
セッションにカウントを保存
セッション名「count」のセッションを1増加させます。セッションがないばあいは、「1」を設定します。
counter.php
<?php
session_start();
if (isset($_SESSION['count'])) {
$_SESSION['count']++;
} else {
$_SESSION['count'] = 1;
}
$count = $_SESSION['count'];
?>
カウントをHTML表示
$count をHTML表示します。また、カウントリンクもつけておきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>カウンター</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div>
<h1>カウンター</h1>
<p><?= $count ?></p>
<a href="counter.php" class="btn btn-primary">カウント</a>
<a href="counter.php?is_clear=1" class="btn btn-outline-primary">クリア</a>
</div>
</body>
</html>
セッション削除
session_terminate() で完全削除
session_terminate() はセッションを完全に削除します。ただし、すべてのセッションが消えてしまうため、アプリに大きく影響するため注意が必要です。
session_terminate()
unset() で指定削除
unset() を利用すると指定のセッションデータを削除できます。
unset($_SESSION['キー']);
GETパラメータでセッション削除
GETパラメータ「is_clear」を取得したときに、セッションを削除します。
<?php
session_start();
session_regenerate_id(true);
//セッション削除
if ($_GET['is_clear']) unset($_SESSION['count']);
if (isset($_SESSION['count'])) {
$_SESSION['count']++;
} else {
$_SESSION['count'] = 1;
}
$count = $_SESSION['count'];
?>
【クリア】リンクを追加します。
counter.php
<div>
<h1>カウンター</h1>
<p><?= $count ?></p>
<a href="counter.php" class="btn btn-primary">カウント</a>
<a href="counter.php?is_clear=1" class="btn btn-outline-primary">クリア</a>
</div>
会員登録(入力画面)
会員登録の各画面にセッション処理を追加していきます。
入力画面
入力画面 input.php を作成します。確認画面から戻ったときに、セッションデータがあれば $user、$errors を取得します。
<?php
session_start();
session_regenerate_id(true);
if (isset($_SESSION['user'])) {
$user = $_SESSION['user'];
}
if (isset($_SESSION['errors'])) {
$errors = $_SESSION['errors'];
}
?>
入力フォーム
input.php のフォームから confirm.php にPOST送信します。また、入力データ、エラーメッセージも表示します。
...
<form action="confirm.php" method="post">
<div class="form-floating mb-3">
<input type="text" class="form-control" name="name" value="<?= @$user['name'] ?>">
<label for="">氏名</label>
<p class="text-danger"><?= @$errors['name'] ?></p>
</div>
<div class="form-floating mb-3">
<input type="email" class="form-control" name="email" value="<?= @$user['email'] ?>">
<label for="">メールアドレス</label>
<p class="text-danger"><?= @$errors['email'] ?></p>
</div>
<div class="form-floating mb-3">
<input type="password" class="form-control" name="password">
<label for="">パスワード</label>
<p class="text-danger"><?= @$errors['password'] ?></p>
</div>
<div class="text-center">
<button class="btn btn-primary">確認</button>
</div>
</form>
...
会員登録(確認画面)
確認画面 confirm.php を作成します。input.php でPOST送信されたデータを取得し、セッションに登録します。
<?php
session_start();
session_regenerate_id(true);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$user = $_POST;
$_SESSION['user'] = $user;
}
?>
入力データ表示
確認画面で入力データを表示します。また、今回は登録処理はしませんが、result.php にPOST送信しておきます。
...
<form action="result.php" method="post">
<label class="form-label fw-bold" for="">氏名</label>
<p><?= $user['name'] ?></p>
<label class="form-label fw-bold" for="">メールアドレス</label>
<p><?= $user['email'] ?></p>
<div class="text-center p-3">
<p>この内容で登録しますか?</p>
<button class="btn btn-primary">登録</button>
<a href="input.php" class="btn btn-outline-primary">戻る</a>
</div>
</form>
...
バリデーション
未入力チェック
データが未入力の場合に、入力画面でエラーメッセージを表示します。
confirm.php の validate() で未入力チェックをします。エラーの場合は、各項目ごとにエラーメッセージを追加した連想配列を返します。
function validate($data)
{
$errors = [];
if (empty($data['name'])) $errors['name'] = 'ユーザ名を入力してください。';
if (empty($data['email'])) $errors['email'] = 'Emailを入力してください。';
if (empty($data['password'])) $errors['password'] = 'パスワードを入力してください。';
return $errors;
}
confirm.php のメイン処理で、validate() を実行してエラーメッセージをセッションに登録します。また、エラーのときは、入力画面にリダイレクトします。
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$user = $_POST;
//バリデーション
$errors = validate($user);
$_SESSION['user'] = $user;
//エラーのセッション登録
$_SESSION['errors'] = $errors;
//入力画面にリダイレクト
if ($errors) {
header('Location: input.php');
}
}
会員登録(完了画面)
完了画面 result.php を作成し、必要なくなったセッションは削除しておきます。また、セッションがない場合は入力ページ input.php にリダイレクトします。
<?php
if (isset($_SESSION['user'])) {
unset($_SESSION['user']);
} else {
header('Location: input.php');
}
?>
完了のメッセージを表示します。
...
<h2 class="h2 text-center p-3">会員登録</h2>
<div class="text-center">
<p>会員登録が完了しました。</p>
<a href="input.php" class="btn btn-outline-primary">戻る</a>
</div>
...
ソースコード
GitHub
input.php
<?php
session_start();
session_regenerate_id(true);
if (isset($_SESSION['user'])) {
$user = $_SESSION['user'];
}
if (isset($_SESSION['errors'])) {
$errors = $_SESSION['errors'];
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>会員登録</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-8">
<h2 class="h2 text-center p-3">会員登録</h2>
<form action="confirm.php" method="post">
<div class="form-floating mb-3">
<input type="text" class="form-control" name="name" value="<?= @$user['name'] ?>">
<label for="">氏名</label>
<p class="text-danger"><?= @$errors['name'] ?></p>
</div>
<div class="form-floating mb-3">
<input type="email" class="form-control" name="email" value="<?= @$user['email'] ?>">
<label for="">メールアドレス</label>
<p class="text-danger"><?= @$errors['email'] ?></p>
</div>
<div class="form-floating mb-3">
<input type="password" class="form-control" name="password">
<label for="">パスワード</label>
<p class="text-danger"><?= @$errors['password'] ?></p>
</div>
<div class="text-center">
<button class="btn btn-primary">確認</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
confirm.php
<?php
session_start();
session_regenerate_id(true);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$user = check($_POST);
$errors = validate($user);
$_SESSION['user'] = $user;
$_SESSION['errors'] = $errors;
if ($errors) {
header('Location: input.php');
}
}
function validate($data)
{
$errors = [];
if (empty($data['name'])) $errors['name'] = 'ユーザ名を入力してください。';
if (empty($data['email'])) $errors['email'] = 'Emailを入力してください。';
if (empty($data['password'])) $errors['password'] = 'パスワードを入力してください。';
return $errors;
}
function check($posts)
{
if (empty($posts)) return;
foreach ($posts as $column => $post) {
$posts[$column] = htmlspecialchars($post, ENT_QUOTES, 'UTF-8');
}
return $posts;
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>会員登録</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-6">
<h2 class="h2 text-center p-3">会員登録</h2>
<form action="result.php" method="post">
<label class="form-label fw-bold" for="">氏名</label>
<p><?= $user['name'] ?></p>
<label class="form-label fw-bold" for="">メールアドレス</label>
<p><?= $user['email'] ?></p>
<div class="text-center p-3">
<p>この内容で登録しますか?</p>
<button class="btn btn-primary">登録</button>
<a href="input.php" class="btn btn-outline-primary">戻る</a>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
result.php
<?php
session_start();
session_regenerate_id(true);
if (isset($_SESSION['user'])) {
unset($_SESSION['user']);
} else {
header('Location: input.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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-8">
<h2 class="h2 text-center p-3">会員登録</h2>
<div class="text-center">
<p>会員登録が完了しました。</p>
<a href="input.php" class="btn btn-outline-primary">戻る</a>
</div>
</div>
</div>
</div>
</body>
</html>
演習
問題1
ログイン認証処理をセッションを使って実装してみましょう。ログインはユーザデータを利用します。
ユーザデータ
$users = [
['id' => 1, 'name' => 'user1', 'email' => 'user1@test.com', 'password' => '1111'],
['id' => 2, 'name' => 'user2', 'email' => 'user2@test.com', 'password' => '2222'],
['id' => 3, 'name' => 'user3', 'email' => 'user3@test.com', 'password' => '3333'],
];
問題2
ログイン認証後に、mypage.php にリダイレクトしてみましょう。mypage.php は認証済みでないとアクセスできないようにします。
問題3
mypage.php からログアウトして、ログイン画面にリダイレクトしてみましょう。mypage.php は認証済みでないとアクセスできないようにします。