PHPプロ!TIPS+

1. セッションハンドラを作成しよう

複数のWebサーバを構築して、個々のWebサーバ内で同一のセッションを使用することはよくあることです。最近のロードバランサには複数のWebサーバを管理する際に一緒にセッションを保持してくれる機能がついているものもあります。そのような機器は値段が高くて、コストを考えると悩ましいところです。

今回はWebサーバを複数持つような場合でも手軽にDBにセッションを保存して管理する方法をご紹介します。

今回、セッションハンドラを使用する目的は、複数のWebサーバ間で同一セッションを管理することです。

まずはセッションデータを保存するテーブルを作成します。テーブル名、フィールド名などは、好きな名前で作成してかまいません。

CREATE TABLE session_t (
session_id VARCHAR(50) NOT NULL,
session_data text,
session_createdate datetime,
PRIMARY KEY (session_id));

次にセッションハンドラの関数を作成します。

<?php
function connect_db() {
  
$db_connect 
    
mysql_connect("host_name""user_name""password"
    or die(
"Could not connect");
  return 
$db_connect;
}


function 
open ($save_path$session_name) {
  global 
$db;
  
$db connect_db();
  return 
true;
}

function 
close() {
  return 
true;
}

function 
read ($id) {
  global 
$db;
  
mysql_select_db("db_name");
    
  
$result mysql_query("SELECT * 
                         FROM session_t 
                         WHERE session_id='
{$id}'");
  if(
mysql_num_rows($result) == 1){
    
$row mysql_fetch_array($result);
    return 
$row['session_data'];
  } else {
    return 
"";
  }
}

function 
write ($id$sess_data) {
  global 
$db;
  
mysql_select_db("db_name");
  
$result mysql_query("SELECT * 
                         FROM session_t 
                         WHERE session_id='
{$id}'");
  if(
mysql_num_rows($result) == 1){
    
$result mysql_query("UPDATE session_t 
                           SET session_data='
{$sess_data}
                           WHERE session_id='
{$id}'");
  }else{
    
$date date('Y-m-d H:i:s');
    
$result mysql_query("INSERT INTO session_t 
                           VALUES('
{$id}' , 
                                  '
{$sess_data}' ,'{$date}')");
  }
  return 
true;
}

function 
destroy ($id) {
  global 
$db;
  
mysql_select_db("db_name");
  
$result mysql_query("DELETE from session_t 
                         WHERE session_id='
{$id}'");

  return 
true;
    
}

function 
gc ($maxlife_time) {
  return 
true;
}

session_set_save_handler 
    
("open""close""read""write""destroy""gc");
?>

セッションハンドラの作成準備が完了したら、セッションを保存して実行するスクリプトを作成します。

<?php
include_once("session_handler.php");

session_start();
if (
$_POST["submit"]) {
  
print_r($_SESSION);
}else{
  
$_SESSION["name"]="1234567";
  
$_SESSION["id"]="123456789";
}
?>
<html>
<body>
<form action="index.php" method="post">
<input type="submit" name="submit" value="TEST">
</form>
</body>
</html>

実行後にセッションのテーブルを確認すると、セッションデータが格納されて、読み込まれたり書き込まれていることがわかるとおもいます。このようにデータベースさえ使用できれば、セッションを管理することができ、Webサーバが複数ある場合でも共通のセッションを使用することが可能になります。

ただ、セッションハンドラはセッションをデータベースで管理することができますが、読み込んだり、書き込んだりとデータベースへのアクセスが増えてしまい、データベースに負荷をかけてしまうのが難点です。

今回は、セッションハンドラとデータベースを使用することでセッションの共通化を行いましたが、ほかにも、NFS(UNIXシステムで利用されるファイル共有システム)などを使用することでも、セッションの共通化問題を解決させることもできます。

複数のWebサーバを使用するときには、手軽に実装できるセッションハンドラをお使いになってみてはいかがでしょうか。

2. PHPで例外処理

例外処理とは、予期しない結果が発生した場合に強制的に別の処理をすることで、たとえばデータベースに接続できない、数値計算でゼロ除算した、ユーザーの入力値が不正の場合など、正常で無い事が起きた場合にそれを復旧する処理のことです。使用するメリットとしては、エラーの対処が簡単になります。

なお、PHPで例外処理を行うにはPHP5以上を使用する必要があります。

では、以下の割り算をする関数を例に説明します。

function div($val1, $val2) {  //割り算
  $ans = $val1 / $val2;
  return $ans;
}

この関数に

$ans = div(10, 0);

と入力すると、ゼロ除算になりますが、PHPではWarningを出すのみで、そのまま処理が続行されてしまいます。このような場合に例外を使用して記述すると下記のようになります。

function div($val1, $val2) {  //割り算
  if ($val2 == 0){
    //例外発生
    throw new Exception("ゼロ除算");
  }
  $ans = $val1 / $val2;
  return $ans;
}

$val2に0が入っている場合に、throwが実行され、例外クラス(Exception)を例外として投げます。

この関数を使用する場合は、例外が発生する部分( $ans = div(10, 0) )を try{ }で囲みます。その後にcatch句を指定し、引数で例外を取ります。

try {
  $ans = div(10, 0);
} catch (Exception $e) {
  echo "エラー:" . $e->getMessage(); //メッセージ表示
  //ここでエラー回復処理
}
//次の処理

catch句の中では例外の通知や回復処理などをしてから正常な場合の処理に戻ります。 また、catch句を指定しない場合はthrowが呼び出されると、基本的に処理は停止します。

また、$eにはExceptionのインスタンスが入っており、値や関数を内部に持っているので、getMessage()でnew Exceptionをしたときにセットしておいた文字列を取り出すことが出来ます。

さて、例外に付いて簡単に説明をしましたが、上記の例ではメリットをあまり感じないかとおもわれます。例外処理はもっと複雑な処理の時に効力を発揮します。

例えば、データベースに接続、sqlを生成、クエリ発行、結果からデータを取得する処理の場合、発生する可能性のある例外はいくつも考えられますが、その中から

・データベースに接続失敗
・クエリ失敗(構文エラーなど)

の2つが発生するときの処理を考えてみましょう。

それらの処理を関数にして、それぞれの関数で処理に失敗した場合は例外を投げるようにすれば、if文などを省くことができ、コードはシンプルになります。

try{
  //接続失敗したら例外を投げる
  db_connect();

  $sql = "select * from hoge";
  //クエリに失敗したら例外を投げる
  $res = query($sql);

} catch (Exception $e){
  echo "エラー発生:" . $e->getMessage();
  exit();
}

ですが、この場合は発生したエラーをまとめて処理してしまうので、接続エラー、クエリエラーで別々の処理をすることは出来ません。

例外の種類で分岐を行うには、

class db_connect_exception extends Exception{
  function __construct(){
    __parent("DB error");
  }
}

Exceptionを継承して独自例外(db_connect_exception、query_exception)を作成し、catch句でそれぞれの例外を指定すれば、発生する例外によって処理の分岐ができます。

try{
  db_connect();

  $sql = "select * from hoge";
  $res = query($sql);

} catch (db_connect_exception $e){
  echo "DB接続エラー";
} catch (query_exception $e){
  echo "クエリエラー";
} catch (Exception $e){
  echo "その他のエラー";
}

また、呼び出している関数内で発生した例外をcatchしない場合、例外はその上位の呼び出し元に投げられます。

function hoge1(){
  try{
    hoge2();
  } catch (Exception $e){
    echo "例外";
  }
}
function hoge2(){
  hoge3();
}
function hoge3(){
  throw new Exception();
}

hoge3が投げた例外をhoge2がcatchしないままhoge1に投げてhoge1でcatchしています。

これらを組み合わせれば、呼び出し元で例外処理を書くだけで済むので、関数内部はシンプルにすることができ、if文などを省いた見通しのよいコードになります。

もし機会があれば使ってみてはいかがでしょうか。

バックナンバーについて

TIPS-MLは、毎週金曜日に更新され、新しい記事が掲載されます。

Tipsꗗy[W 

Pick Up Q&A

Q
動的なURLを静的に見せる方法
 このエントリーをはてなブックマークに追加 
A
普通に考えて、mod_rewrite でしょうね。 http://www.nishishi.com/blog/2006/01/mod_rewrite_url.html...

>>続きを読む

GETのままでは検索エンジンのロボットが拾ってくれなかったためにSEO対策として有効だと言われていますね。

▲解説者:岡本(アシアル株式会社 教育コーディネーター兼 システムエンジニア)