PHPプロ!TIPSメーリングリスト

┏┏┏―― PHPプロ!Tipsメーリングリスト――――――――――――――
┏┏    
http://www.phppro.jp/ 2007/6/1 号

―――――――――――――――――――――――――――――――――

はじめまして。アシアルの門脇です。

3月に専門学校を卒業、4月に入社してから早くも2ヵ月です。
入社当初は緊張しっぱなしの毎日でしたが、最近は徐々に慣れてきて、心地よく
キーボードを叩く毎日です。

さて、そろそろ新人研修も終わって実際の開発現場でやっていかれる方も、そう
でない方もぜひ、このTipsを活用して一味違うソースコードを書いてみてはいか
がでしょうか。

では、第41回Tipsの始まりです。

━ 目次 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 1. preg_quoteで特殊文字をエスケープ
 2. セッションのガーベッジコレクションをテストする
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. preg_quoteで特殊文字をエスケープ

preg系関数は非常に便利で、perl互換の正規表現が使えます。 ユーザーの入力内容のバリデートや文章の整形など、使いかたは様々です。

便利なpreg系関数ですが、検索するパターンに正規表現で用いる特殊文字を使いたい場合は「\」(バックスラッシュまたは円記号)を付けてエスケープする必要があります。

ですが、「ユーザーからの入力値をパターンにしたい時」のように動的に値が変わる場合は、手動でエスケープすることはできませんし、「プログラムの構文を整形したい時」「ファイル名の置換をしたい時」などの場合は、エスケープする文字が多くなりソースコードが読みにくくなってしまいます。

もちろん、ループ文でエスケープする処理を書くこともできますが、preg系関数には特殊文字をエスケープする関数「preg_quote」があるので簡単にエスケープできます。

preg_quoteがエスケープする文字は、

. \ + * ? [ ^ ] $ ( ) { } = ! < > | :

です。preg系関数で用いる特殊文字は一通りエスケープすることができるのですが、"/"はエスケープしてくれません。(これのエスケープ方法は後述します)

例えば、「George is man?」を「George is man.」に置換したいという場合を考えてみます。

検索パターン($pat)には「man?」を指定して、置換する文字列($rpl)には「man.」を指定しています。

$txt = "George is man?";
$pat = "/man?/";
$rpl = "man.";
$str = preg_replace($pat, $rpl, $txt);

この場合、予想通りに動作するようにも見えますが、preg_replaceの第一引数は 「/man?/」なので、「ma」、「man」にマッチしてしまい、結果は「man.?」になってしまいます。

予想通りに動作させるには、検索パターンの「man?」を「man\?」にする必要があります。この処理を自動的にやってくれるのが、preg_quoteです。

$txt = "George is man?";
$pat = "/man?/";
$rpl = "man.";
$str = preg_replace(preg_quote($pat), $rpl, $txt); //preg_quoteを使用

のようにすれば、「?」をエスケープして「man\?」となり、「man?」にマッチするので無事に「Gorge is man.」に置換できます。

便利なpreg_quoteですが、上記の例ではデリミタである"/"は自動的にはエスケープしないので"http://www"のようなものには使えません。このような場合は、preg_replaceの第二引数を使用します。

第二引数にはデリミタとして使用するエスケープしたい任意の文字列を指定できるので、ここで"/"を指定すれば、

$txt = "http://example.com/main/pictures/";
$pat = "http://example.com/";
$rpl = "/var/www/";
$str = preg_replace("/" . preg_quote($pat, "/") . "/", $rpl, $txt);

のようなコードでも正常に動作させることができます。

このpreg_quoteは、プログラムを予想通りに動作させること以外に、

//preg_quote非使用
$pat =
"//\.\.\/hoge\/piyo-fuga\/\[07-02-02\]\(subtitle\)backup\.tar\.bz2//";

//preg_quote使用
$pat = "/" .
preg_quote("../hoge/piyo-fuga/[07-02-02](subtitle)backup.tar.bz2", "/")
. "/";

このように、エスケープすると見づらくなるソースもわかりやすくなります。

ひとつエスケープを忘れたのに気づかずに誤動作を起こして、デバッグに時間が かかってしまうなどのリスクを軽減できます。

ループ文を使用してエスケープ処理を書くより、この関数を使ったほうが無駄な手間を省いてスマートに記述できます。

もし、preg系関数のエスケープ処理を書く機会があれば、使ってみてはいかがでしょうか。

2. セッションのガーベッジコレクションをテストする

普段、何気なく使っているセッション機能や変数。理屈では理解していても、本当に思ったとおりに動作しているのでしょうか?

特に、ガーベッジコレクションに焦点をあてて挙動を見てみましょう。

まずrecommendの設定を使うと、

session.gc_probability = 1
session.gc_divisor     = 100
session.gc_maxlifetime = 1440

となっています。

念のため、上記設定の挙動を再確認すると、

gc_probability ÷ gc_divisor × 100 = ガーベッジコレクションが行われる確率(%)

となり、上記設定だと100回のアクセスに1回ガーベッジコレクションが行われる確率となります。

また、gc_maxlifetimeは、セッションの有効期間(秒)を示しています。
つまり、24分間放置すると次に使おうとしてもセッションが切れてしまいます。

ここでテストを行いやすいように各設定を変更します。

session.gc_probability = 1
session.gc_divisor     = 3
session.gc_maxlifetime = 30

phpinfo()のsessionの設定

phpinfo()のsessionの設定

こうすると、3回に1回のアクセスで、30秒以上経過した古いセッションファイルがガーベッジコレクションの機能により、削除されるのではないでしょうか。

テストを正確に行う為、セッションファイルを保存する/tmp内も全て削除しておきます。

<?php
session_save_path
("/tmp");
session_start();
session_regenerate_id();
$session_dir scandir(session_save_path());

print_r($session_dir);

このようなスクリプトを組んでおき、3回アクセス。ちなみにsession_regenerate_id()は、無駄なセッションファイルを生成する為の仕掛けです。

Array
(
    [0] => .
    [1] => ..
    [2] => sess_42090b4559946e31497499c765c1fb3c
    [3] => sess_8a5662097c1229722a315af54208c45b
    [4] => sess_e790cda62c8f755f9330a58a826d47e9
)

最後のアクセスから30秒以上経過するのを待ち、再度アクセスを試みます。

Array
(
    [0] => .
    [1] => ..
)

ファイルが消えました。

予想通り30秒以上経過すると、ゴミと判断されてガーベッジコレクション機能が働いてくれました。

念のためですが、セッションのファイルが生成されるタイミングはスクリプトが終了してからなので、この直後に新たなセッションファイルが1つ作成されています。とてもスッキリしました。

次に、ガーベッジコレクションが行われるタイミングです。

テストは初の試みなので、おそらくsession_start()のタイミングで行われていることを想定し、

<?php
session_save_path
("/tmp");

$old_session_dir scandir(session_save_path());
session_start();
$new_session_dir scandir(session_save_path());

session_regenerate_id()

print_r($old_session_dir);
print 
"\n";
print_r($new_session_dir);

Array
(
    [0] => .
    [1] => ..
    [2] => sess_06a4c33a68f30a2aaff14ee910d2d7df
    [3] => sess_1111b3fc918ab8649739427241d1ee87
)

Array
(
    [0] => .
    [1] => ..
    [2] => sess_06a4c33a68f30a2aaff14ee910d2d7df
    [3] => sess_1111b3fc918ab8649739427241d1ee87
)

間隔を置かずに、2回連続アクセスするとこのようになりました。

ここでまた30秒以上の経過を待ってアクセスを行います。

Array
(
    [0] => .
    [1] => ..
    [2] => sess_06a4c33a68f30a2aaff14ee910d2d7df
    [3] => sess_1111b3fc918ab8649739427241d1ee87
    [4] => sess_e3d19f50de88e45422aea1d0a087ec67
)

Array
(
    [0] => .
    [1] => ..
)

予想通りの結果でした。session_start()内でガーベッジコレクションが行われています。

次に、ガーベッジコレクションは、「sess_」から始まるファイル以外も削除するのかの実験です。

<?php
session_save_path
("/tmp");
session_start();
session_regenerate_id();
print_r(scandir(session_save_path()));

これだけの処理を記述しておき、全然違う名前のファイルを作成し、ガーベッジコレクション機能が「ゴミ」と判断するかを試します。

但し、ファイルを作成してから30秒以上経過してからアクセスを行います。

まずは、/tmp/a.phpにファイルを作成(Apacheの権限で作成)30秒待ってアクセスします。

Array
(
    [0] => .
    [1] => ..
    [2] => a.php
    [3] => sess_144ce6553ac72000ccc7bdf7ea8a38de
    [4] => sess_277f838f8ab59985c2e6a8611374ccb2
    [5] => sess_c1f4d859de5695c8005ca4b2e75fd802
    [6] => sess_f996d6c24c01c6aca2e97e763b2f76dd
)

消えません。何度アクセスしても、a.phpは健在のままでしたので、sess_から始まるファイル以外は削除しないようです。

最後に/tmp/sess_aaaaaと、セッションファイルに似せたファイルを勝手に作成した場合は、ガーベッジコレクションの機能により削除されました。いくつかの疑問が解決してスッキリしました。テストが終わったので、設定を元の値に戻しましょう。