php講座

セッション

事例研究

ログインの上、適当な商品を選択して「購入する」ボタンを押せ。

その次のページでブラウザから「ソース」を確認してみよう。

パスワードがソース上に書かれているのはどちらか。そしてその問題点は何か。

※セッション不使用バージョンは<input type="hidden">を用いている

セッションとは

ページまたぎのデータ受渡

Webサイトでページを遷移してもそのデータを受け継ぎたいケースがある。例えば会員制サイトならばログイン情報など、ショッピングサイトでは購入品情報など。

しかしプログラム上、ページをまたぐと変数の受渡はされない(変数の寿命は最長でページ内)。このため、ページまたぎの情報をやりとりするための仕掛けとして複数の方法が用意されている。

URL経由($_GET[])
http://…/?変数名=値
フォームタグ($_GET[], $_POIST[])
<input type="hidden" name="変数名" value="値">
クッキー(Cookie)
クライアントPCに情報を保管させる。本講では扱わない。
セッション
サーバ側で情報を保管し、クライアントPCには接続情報のみ保管させて連携を取る。

ここでは最後のセッション管理について扱う。

セッションの意義

セッション以外の情報の授受には各々難点がある。

URL経由、フォームタグ

フォームタグやURL経由の情報のやりとりは情報の中身が外から見えてしまうため、ログイン情報などの受渡にはふさわしくない。

複雑なデータ構造(配列やオブジェクト)をそのまま受渡できない。

ページを遷移するごとに逐一遷移先にデータを明示的に渡さなければならない。

クッキー

保存できる情報量に制限がある。クライアントの環境に依存する要素が大きい。

セッション

一定時間のみサーバ側が情報を保持する仕組みのため、恒久的な情報保持には適さない。

その一方ログイン情報など、テンポラリな情報のやりとりを行うには非常に便利に使える。会員制サイトやショッピングサイトのほとんどは基本的にこのセッションの機能を用いている。

配列やオブジェクトをそのまま受け渡しできる。

PHPでのセッション使用法

セッションを開始する
session_start();

※セッションを用いる全ページの最初に書く

セッションにデータを格納する
$_SESSION['変数名'] = $変数名;
セッションからデータを取り出す
if(isset($_SESSION['変数名'])){
    $変数名 = $_SESSION['変数名'];//セッションにデータがあれば取り出す
}else{
    //セッションにデータが無かったときの処理
}

※一度セッションに格納したデータはページを何度遷移しても保持される(フォームやURL経由はそうはいかない)。

セッションを終わらせる
// セッション変数(プログラム内)を全て解除する
$_SESSION = array();

// セッションクッキー(クライアント側)を削除する。
if (isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time()-3600, '/');
}

// 最終的に、セッションを破壊(サーバ側)する
session_destroy();

※一見ややこしいがコピペで対応できる。

ソース確認

Customer.class
<?php
/*
 * session1.php,session2.phpで用いるCustomerクラス
 * ログイン名、パスワード、購入品(配列)を保持している
 */
class Customer{
    /***************ログイン情報****************/
    //user_id: user
    //user_name: 高橋
    //password: password218
    //本来はDBなどから持ってくる
    private $valid_user_id = 'user';
    private $valid_user_name = '高橋';
    private $valid_password = '$2y$10$MqkeP6rHjdnsGzkF0lTzvO03eBd8kL2MZ2LGvLkDUXbUpYM4UhHIO';//make_crypt.phpで作成した暗号化済みパスワード
    /*******************************************/

   /*
    * ログイン名
    */
    private $user_name;

   /*
    * 正しくログインしたか
    */
    private $is_valid_user;

   /*
    * 購入品(配列で複数品目を保持)
    */
    private $products;
    
   /*
    * コンストラクタ
    * @param string $name ログイン名
    * @param string $pass パスワード
    */
    public function __construct($user_id, $password){
        $this->user_name = $this->valid_user_name;

        if($user_id == $this->valid_user_id && password_verify($password,$this->valid_password)){
            $this->is_valid_user = true;
        }
    }

   /*
    * 商品購入
    * 購入品をproducts配列に保存
    * @param string $product 購入品
    */
    public function buyProducts($product){
        $this->products[] = $product;
    }

   /*
    * 商品リストを空にする
    */
    public function clearProducts(){
        $this->products = array();
    }

   /*
    * ログイン情報取得
    * @return boolean
    */
    public function is_valid_user(){
        return $this->is_valid_user;
    }

   /*
    * ログイン名取得
    * @return string
    */
    public function getName(){
        return $this->user_name;
    }

   /*
    * 購入商品配列取得
    * @return array
    */
    public function getProducts(){
        return $this->products;
    }
}
?>
Product.class
<?php
class Product{
    /***************ログイン情報****************/
    //本来はDBから取り出すデータ
    private $goods = array(
        'iPhone SE' => 64800,
        'iPhone 6s' => 86800,
        'iPhone 6s Plus' => 98800,
        'iPhone ケース' => 3500,
        'ドック' => 19800,
        'イヤホン' =>15600,
        '1年保証' => 19800,
        '3年保証' => 39800,
        '永久保証' => 498000
    );
    /*******************************************/

    public $name;
    private $price;

    public function __construct($name){
        $this->name = $name;
        $this->price = $this->goods[$this->name];
    }

    public function getName(){
        return $this->name;
    }

    public function getPrice(){
        return $this->price;
    }
}
?>
login.php
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="initial-scale=1.0">
   <title>会員制ショッピングサイトログイン画面</title>
   <link rel="stylesheet" href="default.css">
</head>
<body>
<header>
<h1>会員制ショッピングサイトログイン画面</h1>
</header>
<article>
<form action="session1.php" method="POST" autocomplete="OFF">
<ul>
<li><label for="user_id">会員ID</label><input type="text" size="10" name="user_id" id="user_id"></li>
<li><label for="password">パスワード</label><input type="password" size="10" name="password" id="password"></li>
</ul>
<button type="submit" name="login" value="login">ログインする</button>
</form>
<dl class="info">
<dt>サンプル会員</dt>
<dd>
会員ID: user (会員名:高橋)<br>
パスワード: password218<br>
</dd>
</dl>
</article>
<footer>
坂井商会
</footer>
</body>
</html>
session1.php
<?php
header("Cache-Control: private");
session_cache_limiter('none');
require_once('Customer.class');
session_start();

$user_id = $_POST['user_id'];
$password = $_POST['password'];

$customer = new Customer($user_id,$password);

if(!$customer->is_valid_user() && !$_POST['login']){
    exit('不正アクセスです。');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8" />
   <title>セッション</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="default.css">
</head>
<body>
<header>
<h1>会員制ショッピングサイト</h1>
<?php
if(!$customer->is_valid_user()){
    print '<p><strong>idかパスワードを間違っています。</strong></p>';
    print '<p><a href="login.php">ログインをやり直す</a></p></header>';
}else{
?>
<nav>
<p style="text-align:right">ようこそ!<?php print $customer->getName();?>さん <a href="logoff.php">ログオフ</a></p>
</nav>
</header>
<article>
<h2>購入フォーム</h2>
<form action="session2.php" method="POST">
<fieldset style="width:10em;box-shadow:0.1em 0.2em 0.2em 0.1em #c0c0c0">
<legend>商品選択</legend>
商品:<select name="goods1">
<option value="" >未選択</option>
<option value="iPhone SE" >iPhone SE</option>
<option value="iPhone 6s" >iPhone 6s</option>
<option value="iPhone 6s Plus" >iPhone 6s Plus</option>
</select><br>
オプション購入:<select name="goods2">
<option value="" >未選択</option>
<option value="iPhone ケース" >iPhone ケース</option>
<option value="ドック" >ドック</option>
<option value="イヤホン" >イヤホン</option>
</select><br>
サービス:<select name="goods3">
<option value="" >未選択</option>
<option value="1年保証" >1年保証</option>
<option value="3年保証" >3年保証</option>
<option value="永久保証" >永久保証</option>
</select><br>
<button type="submit" name="buy" value="true">購入する</button>
</form>
<?php
$_SESSION['customer'] = $customer;
?>
</article>
<?php
}
?>
<footer>
坂井商会
</footer>
</body>
</html>
session2.php
<?php
header("Cache-Control: private");
session_cache_limiter('none');
require_once('Customer.class');
require_once('Product.class');
session_start();

if(isset($_SESSION['customer'])){
    $customer = $_SESSION['customer'];
    $customer->clearProducts();
    if(!$customer->is_valid_user()){
        exit('不正アクセスです。');
    }
}else{
    exit('不正アクセスです。');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8" />
   <title>セッション</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="default.css">
</head>
<body>
<header>
<h1>会員制ショッピングサイト</h1>
<nav>
<p style="text-align:right">ようこそ!<?php print $customer->getName();?>さん <a href="logoff.php">ログオフ</a></p>
</nav>
</header>
<article>
<form action="session3.php" method="POST">
<h2><?php print $customer->getName();?>さんのカート</h2>
<?php
if($_POST['buy']){
    if($_POST['goods1']){
        $goods1 = new Product($_POST['goods1']);
    }
    if($_POST['goods2']){
        $goods2 = new Product($_POST['goods2']);
    }
    if($_POST['goods3']){
        $goods3 = new Product($_POST['goods3']);
    }
?>
<table>
<?php
if($goods1){
    $customer->buyProducts($goods1);
}
if($goods2){
    $customer->buyProducts($goods2);
}
if($goods3){
    $customer->buyProducts($goods3);
}
$pay = 0;

if(count($customer->getProducts())>0){
    foreach($customer->getProducts() as $product){
        $pay += $product->getPrice();
        print '<tr><td>'.$product->getName().'</td><td>'.number_format($product->getPrice()).'円</td></tr>';
    }
}
?>
</table>
<p class="pay">合計金額:<?php print number_format($pay);?>円</p>
<?php
}
$_SESSION['customer'] = $customer;
if(count($customer->getProducts()) > 0){
?>
<button type="submit">購入決定</button>
<?php
}else{
    print '<p>なんか買えや!</p>';
}
?>
<button type="button" onclick="history.back()">購入フォームに戻る</button>
</form>
</article>
<footer>
坂井商会
</footer>
</body>
</html>
session3.php
<?php
header("Cache-Control: private");
session_cache_limiter('none');
require_once('Customer.class');
require_once('Product.class');
session_start();

$postage = 55000;
$tax = 0.08;

if(isset($_SESSION['customer'])){
    $customer = $_SESSION['customer'];
    if(!$customer->is_valid_user()){
        exit('不正アクセスです。');
    }
}else{
    exit('不正アクセスです。');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8" />
   <title>セッション</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="default.css">
</head>
<body>
<header>
<h1>会員制ショッピングサイト</h1>
<nav>
<p style="text-align:right">ようこそ!<?php print $customer->getName();?>さん <a href="logoff.php">ログオフ</a></p>
</nav>
</header>
<article>
<h2><?php print $customer->getName();?>さんの購入品</h2>
<table>
<?php
$pay = 0;
if(count($customer->getProducts())>0){
    foreach($customer->getProducts() as $product){
        $pay += $product->getPrice();
        print '<tr><td>'.$product->getName().'</td><td>'.number_format($product->getPrice()).'円</td></tr>';
    }
    $pay += $postage;
    print '<tr><td>送料</td><td>'.number_format($postage).'円</td></tr>';
    print '<tr><td>税金</td><td>'.number_format($pay * $tax).'円</td></tr>';
?>
</table>
<p class="pay">支払額は送料・税込み<?php print number_format($pay * (1+$tax));?>円です。毎度あり</p>
<?php
}else{
    print '<p>なんか買えや!</p>';
}
?>
</article>
<footer>
坂井商会
</footer>
</body>
</html>
logoff.php
<?php
session_start();
// セッション変数を全て解除する
$_SESSION = array();

// セッションクッキーを削除する。
if (isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time()-1800, '/');
}

// セッションを破壊する
session_destroy();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8" />
   <title>ログオフ画面</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="default.css">
</head>
<body>
<header>
<h1>会員制ショッピングサイト</h1>
</header>
<article>
<p>ログオフ完了しました。</p>
<dl class="info">
<dt>再度ログインする</dt>
<dd>
<ul>
<li><a href="login.php">ログインページ</a></li>
</ul>
</dd>
<dt>ログインページをスルーして直接別ページを見る。</dt>
<dd>
<ul>
<li><a href="session1.php">session1.php</a></li>
<li><a href="session2.php">session2.php</a></li>
<li><a href="session3.php">session3.php</a></li>
</ul>
</dd>
</dl>
</article>
<footer>
坂井商会
</footer>
</body>
</html>