PHPでセッションを利用するための設定仕事で使える魔法のLAMP(43)

今回は、ちょっと大きなWebアプリケーションを作ることになると、必要になる「セッション」の機能をPHPで利用する方法を解説します。さらに、これまで説明しきれなかったけど、設定しておいたほうがよい項目についても解説します(編集部)

» 2012年02月23日 00時00分 公開
[山口晴広株式会社イメージズ・アンド・ワーズ]

別々のHTTPアクセスの間でデータを共有させる

 第37回より、PHPの実行時設定について解説を続けてきましたが、今回でPHPについての解説は最後です。PHPの設定ディレクティブはまだまだたくさんありますが、LAMP環境を構築するときに設定しておくべき内容はおおよそ網羅できたと思います。残りの設定ディレクティブは、PHPプログラムの開発というテーマに関係するものになります。

 今回はセッションに関する設定について解説します。そして、PHPに関する設定の中でも、これまで紹介する機会を作れませんでしたが、設定しておいた方がよいものがいくつかありますので、これも取り上げます。

 セッションについてはご存じの方も多いと思いますが、軽く説明しておきます。セッションとは、簡単に言うと、同じブラウザからの一連のHTTP(HyperText Transfer Protocol)アクセスの間で、データを共有するための仕組みです。

 HTTPのアクセスは1つ1つ独立していて、それぞれのアクセス同士には何の関係もありません。しかし、例えばログインをするようなシステムでは、そのシステムへ接続してくるHTTPアクセスのそれぞれにログイン中であることを認識させなければなりません。HTTPというプロトコルの仕組みだけではこのようなことはできません。

 そこでセッションを使うと、Webブラウザから実行される複数のHTTPアクセスと、ログイン中のユーザーとを関連付けることができるようになります。この関連付けには、クッキーを利用します。クッキーにセッションIDと呼ぶ識別情報を保存することで実現するのです(図1)。

図1 セッションの処理の流れ 図1 セッションの処理の流れ

 PHPは、sessionエクステンションでセッションに対応しています。このエクステンションは、標準で有効になっています。$_SESSIONという配列変数に値を保存することで、セッションにデータを保存することが可能になります。

 変数に保存したセッションデータは、エクステンションがファイルやデータベースなどのストレージに保存します。2回目以降のHTTPアクセスでは、ストレージに保存してあるデータを読み出して$_SESSIONに格納します。このような仕組みで、あたかも$_SESSIONという変数を別々のHTTPアクセスの間で共有しているように振る舞うわけです。

セッションデータはどこに保存する?

 Webアプリケーションでセッションを使うときは、最初にセッションデータを保存する場所について考えなければなりません。標準設定では1つ1つのセッションごとに、ファイルを1つずつファイルシステムに保存するようになっています。セッションデータを保存する場所と方法は、開発者がプログラムを開発することでいろいろ変えることができます。例えば、MySQLなどのデータベースや、memcachedなどのメモリキャッシュシステムなどにも保存できます。

 セッションの保存先を決めるときは、以下に挙げる要因を考えるとよいでしょう。まずは同時利用者数、つまり同時に処理するセッション数です。そして、時間当たりのアクセス数。さらに、セッションに保存するデータの大きさです。ほかにもセッションの保存先の決定に影響する要因はありますが、まずはこの3つの要素を考えるとよいでしょう。

 また、複数のWebサーバによる並列処理で負荷分散をする場合は、保存したセッションデータはそれぞれのWebサーバで共有できなければなりません。ファイルに保存してしまっては共有できません。専用のデータベースサーバを用意して保存するなどの手段を考える必要があります。

 今回は並列処理が必要になるほど負荷がかかる状況は想定せず、シンプルにファイルに保存することにして、セッションを利用する方法を解説します。ファイルに保存する場合、標準の状態では保存ディレクトリは/tmpとなっています。

 第40回でユーザーがWebブラウザからアップロードしたファイルの保存先が標準で/tmpになっていることの問題点を解説しました。セッションデータを保存するディレクトリも/tmpです。同じような問題が発生することは十分考えられます。

 第40回では、大きく分けて2つのポイントを解説しました。/tmpがtmpfsという一時ファイル用のRAMディスクになっていることがあり、ここに大きなファイルを置くとメモリ領域を圧迫してしまうこと。もう1つは、/tmpが/(ルート)を含むパーティションの一部となっていると、/varなどのOSに必要なストレージ領域を圧迫する可能性があるということです。

 セッションの保存先を決めるときも、やはり第40回で解説したようなポイントに注意する必要があります。ただし、ユーザーがアップロードしたファイルと、セッションデータを比較すると、アクセス頻度やデータサイズがまるで違います。まず、セッションデータはHTTPのアクセスがあるたびに読み書きが発生します。ディスクよりメモリに保存した方が、性能を上げられる可能性が上がるということになります。

 そして、セッションのデータはアップロードファイルに比べればデータサイズは小さくなります。つまり、セッションデータをtmpfsの領域に保存するようにしても、メモリ領域を大量に消費することはありません。メモリに余裕があるのなら、tmpfsの領域に保存するようにしておくのがよいでしょう。

 では、/tmpがtmpfsであればデフォルトの/tmpのままでよいかというと、そうでもありません。セッションはバーチャルホストごとにできるデータですので、バーチャルホスト内で共有できるように、それぞれのバーチャルホストのディレクトリに保存すべきです。それに/tmpはOSが一時ファイルに使います。やはりセッションデータが領域を圧迫するという問題が発生することも考えられます。

 以上で説明した事情を考えて、この連載では各バーチャルホストにセッション保存ディレクトリをtmpfsで作成することにします。

セッション用のtmpfs領域を作る

 本連載では、「/srv/httpd/バーチャルホスト名/」という構成でバーチャルホストごとにディレクトリを用意しました。さらに、この中にログを保存するディレクトリ「logs」や、Web公開ディレクトリ「webspace」があります。また、前回はPHPが読み書きするファイルを置くために「phpdata」というディレクトリを作成しました。これらのディレクトリと同じようにセッションデータを置く「phpsession」を作ることにします。まずは以下のようにコマンドを実行してディレクトリを用意します。

$ sudo mkdir /srv/httpd/www3026ub.sakura.ne.jp/phpsession

 続いて、このディレクトリにtmpfsの領域をマウントします。マウントするだけならmountコマンドで済むのですが、OSを再起動しても起動するたびに自動的にtmpfsの領域をマウントするように、/etc/fstabという設定ファイルに次の行を加えます。

tmpfs /srv/httpd/www3026ub.sakura.ne.jp/phpsession tmpfs size=32m,mode=700,uid=daemon,gid=daemon 0 0

 行の中ほどに「size=32m」という記述があります。これがtmpfsのサイズになります。ここでは32Mbytesに設定しました。「mode=700」はマウント後のパーミッションを表しています。「700」は所有者のみの読み書きとなります。「uid=daemon,gid=daemon」はディレクトリの所有ユーザーとグループを示しています。セッションはPHPが読み書きしますので、所有者はApacheの実行ユーザーと一致させなければなりません。

 /etc/fstabに行を追加したら、マウントするために次のコマンドを実行するか、OSを再起動してください。

$ sudo mount /srv/httpd/www3026ub.sakura.ne.jp/phpsession

 これでphpsessionディレクトリをtmpfsでマウントできました。ストレージの状況を確認しましょう。

$ df -Th
Filesystem    Type    Size  Used Avail Use% Mounted on
/dev/hda2     ext3     17G  3.1G   13G  20% /
/dev/hda1     ext3     99M   36M   58M  39% /boot
tmpfs        tmpfs    753M     0  753M   0% /dev/shm
/dev/hdb1     ext3     30G  4.3G   24G  16% /home
tmpfs        tmpfs     32M     0   32M   0% /srv/httpd/www3026ub.sakura.ne.jp/phpsession

 さらに、バーチャルホストのディレクトリ内にあるそれぞれのディレクトリのパーミッションも確認しましょう。

$ ls -l /srv/httpd/www3026ub.sakura.ne.jp
total 20
-rw-r--r-- 1 root   root    587 Feb  9 18:29 apache.conf
drwxr-xr-x 2 root   root   4096 Feb  2 05:23 logs
drwxr-xr-x 2 daemon daemon 4096 Feb  9 18:29 phpdata
drwx------ 2 daemon daemon   40 Feb 16 04:05 phpsession
drwxr-xr-x 3 root   root   4096 Feb  9 18:29 webspace

 期待通り、32Mbytesの領域としてマウントされ、またdaemonからのみ読み書きできるディレクトリとなっています。これで保存ディレクトリができました。次にバーチャルホストごとのApache設定ファイルである「/srv/httpd/www3026ub.sakura.ne.jp/apache.conf」に次の行を追加します。

PHP_Value session.save_path /srv/httpd/www3026ub.sakura.ne.jp/phpsession

 「session.save_path」はセッションデータを保存する場所を設定するディレクティブです。このディレクティブは、sessionエクステンション固有のディレクティブです。ディレクティブの名前の先頭に「session」とエクステンション名が付いていることがその事実を示しています。

セッションの動作を試す

 セッションが働いているのか試してみましょう。まず、session_set.phpという名前で次のプログラムをWeb公開ディレクトリに設置します。セッションにGETメソッドで得たリクエストデータを保存するものです。

<?php
session_start();
$_SESSION['test'] = $_GET['test'];
?>

 もう1つ、セッションの内容を表示するプログラムであるsession_get.phpをWeb公開ディレクトリに設置します。

<?php
session_start();
echo $_SESSION['test'];
?>

 curlコマンドで確認してみましょう。ただし、セッションはクッキーを使うので、クッキーの保存と送信を示すオプションを忘れないでください。「-c ファイル名」が保存で、「-b ファイル名」が送信です。

$ curl -c cookie.dat -b cookie.dat 'http://www3026ub.sakura.ne.jp/session_set.php?test=foobar'
$ curl -c cookie.dat -b cookie.dat http://www3026ub.sakura.ne.jp/session_get.php
foobar

 1回目のリクエストで送信したデータ(「?test=foobar」)が、セッションを経由して2回目のリクエストで表示されました。セッションはちゃんと動作しています。セッション保存ディレクトリを見てみましょう。

$ sudo ls /srv/httpd/www3026ub.sakura.ne.jp/phpsession
sess_7a0fbe05e6315cd9a617c84f4c8f043b

 ファイルが作成されていることが確認できます。ついでにそのファイルの中身を見たところ、次のようになっていました。

$ sudo cat /srv/httpd/www3026ub.sakura.ne.jp/phpsession/sess_7a0fbe05e6315cd9a617c84f4c8f043b
test|s:6:"foobar";

 セッション変数である$_SESSIONに格納した値が保存されている事が見て取れます。セッションについての解説は以上です。本稿の残りの部分では、今まで説明しきれなかったPHPの実行時設定をいくつか取り上げ、設定法を解説します。「今まで説明しきれなかった」と表現すると、あまり重要な設定項目ではないと思ってしまう人もいるかもしれませんが、以下で説明する設定項目はすべてきちんと設定しておくべきものです。

タイムゾーンの指定

 本連載では、設定を確認するために、phpinfo()を何度も実行してきましたが、その途中でエラーメッセージが出ていたことに気付いた方もいるかもしれません。これはタイムゾーンを設定していないため、日付データを操作するdateエクステンションが出力している警告メッセージです。ログには次のようなメッセージが残ります。

[Thu Feb 16 06:11:08 2012] [error] [client 210.255.86.78] PHP Warning:  phpinfo() [<a href='function.phpinfo'>function.phpinfo</a>]: It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Asia/Tokyo' for 'JST/9.0/no DST' instead in /srv/httpd/www3026ub.sakura.ne.jp/webspace/phpinfo.php on line 2

 php.iniに次の行を加えれば、エラーメッセージの出力はなくなります。エラーメッセージ中にあるとおり「date.timezone」ディレクティブでタイムゾーンを指定するのです。

date.timezone = Asia/Tokyo

特定のファンクションを無効にする

 脆弱な部分を作ることにつながる可能性が高い、リスクのあるPHPのファンクションはそもそも使わないとする開発方針もあります。それを強制するのが「disable_functions」ディレクティブです。使用不可能にするファンクションの名前をカンマ区切りで指定します。ただし、この設定はphp.iniにしか記述できないことに注意してください。バーチャルホストごとに設定を上書きすることもできません。

 disable_functionsディレクティブで無効にしておきたいファンクションとしては、外部プログラムの実行に関係するものが挙げられます。これは前回、開けるファイルを限定したのとまったく同じ理由による判断です。バグやミスによって、リクエストデータで実行ファイル名が制御できてしまうと、任意のコマンドが実行できてしまうためです。

 disable_functionsディレクティブは、以下のように設定しておくと良いでしょう。

disable_functions = exec,shell_exec,system,popen,proc_open,passthru,show_source,parse_ini_file,dl

まとめ

 今回の解説内容を考えてphp.iniの内容をまとめると、次のようになります。

memory_limit = 32M
max_execution_time = 30
expose_php = Off
magic_quotes_gpc = Off
variables_order = GPCS
register_long_arrays = Off
register_argc_argv = Off
post_max_size = 64K
max_input_vars = 100
upload_tmp_dir = /home/uploads
upload_max_filesize = 1
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL | E_STRICT
open_basedir = /srv/httpd
allow_url_fopen = Off
date.timezone = Asia/Tokyo
disable_functions = exec,shell_exec,system,popen,proc_open,passthru,show_source,parse_ini_file,dl

 次回からはMySQLの解説に入ります。

著者紹介

株式会社イメージズ・アンド・ワーズ
代表取締役
山口晴広(やまぐち はるひろ)



「仕事で使える魔法のLAMP」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。