実用プログラムの第一歩として、よくあるアクセスカウンターを作ってみましょう。
その前にファイルの扱いについて簡単に説明しておきます。
とりあえず初心者が躓く点として、PHPはファイルを直接扱うことができず、ファイルポインタという変数を通して扱わなければならないことがあげられます。
$fp = fopen("counter.txt","r+");
というふうに、counter.txtを$fpという変数に代入し、以後変数を通してファイルを操作することになります。
PHPによるファイルの操作はけっこう不自由で面倒なので、今後は徐々にデータベースを使用するようにしていきます。
以下の2ファイルを用意します。
counter.txt
0 |
counter.php
<?php //ファイルを書き込み可能状態で開く $fp = fopen("counter.txt","r+"); //ファイルから1行読み込む $count = fgets($fp); //カウンターを1増やす ++$count; //ファイルポインタを戻す rewind($fp); //ファイルに書き込む fputs($fp,$count); //ファイルを閉じる fclose($fp); ?> <html> <head></head> <body> アクセスカウンター<br /> <div align="center"><?php print($count); ?> </div> </body> </html> |
counter.txtは改行を入れず、アップロード時にパーミッションを666に変更します。
注意すべきは一点、rewindです。
ファイルポインタは、今自分がどの場所にいるかという情報、要するにカーソルの位置を記憶しています。
fgets等でファイルの内容を取得すると、その場所までファイルポインタの位置が移動します。
この場合「1」というファイルの中身を読んだ後、「1」の後ろの部分になるということです。
その状態で書き込みを行ってしまうと、ファイルの中身は「12」ということになってしまうので、一旦rewindでファイルポインタを最初に戻しています。
さて、上のプログラムはとりあえず動くのですが、問題があります。
ほぼ同時に二人がアクセスした場合を考えてみます。
A | B |
fopen | |
fgets(=1) | fopen |
$count++(=2) | fgets(=1) |
fputs(=2) | $count++(=2) |
fclose | fputs(=2) |
fclose |
二人がアクセスしたにもかかわらず、カウントが1しか増えていません。
同時にアクセスされた場合の処理を考えていないからです。
アクセスカウンター程度なら一人や二人ずれたところでどうでもいいですが、これがたとえば銀行の送金処理で起こったりしたらとんでもないことになります。
このような問題を防ぐためには、同時に一人しかファイルにアクセスできないようにすればいいわけです。
PHPには簡単にファイルロックを行える関数flockがあるので使用することにしましょう。
counter2.php
<?php //ファイルロック |
こうすることで、ほぼ同時にリクエストが来た場合でも後者が待たされるので、正しい結果を出すことができます。
A | B |
fopen | |
fgets(=1) | ↓ |
$count++(=2) | ↓ |
fputs(=2) | ↓ |
fclose | fopen |
fgets(=2) | |
$count++(=3) | |
fputs(=3) | |
fclose |
ちなみにロック解除のflock($fp, LOCK_UN)は、fcloseの際に自動的に行われるので書かなくてもいいです。
上のプログラムでまあとりあえず配布可能レベルにはなっていますが、できれば「ファイルを作成してパーミッションを云々」というような余計な手間をかけさせないほうがいいでしょう。
自動作成するようにしてみます。
defineは定数を定義するもので、ディレクトリ名やファイル名等、ファイル全体で使用し、定義後は変更しないデータを突っ込んでおきます。
counter3.php
<?php //カウンターファイルが存在するかどうかで分岐 |
ファイルが存在しない場合のfopenのみエラー処理を行っている理由は、パーミッションの関係でファイルの作成が行えない可能性があるからです。
fopen,flock,fgets,fputs,fcloseは返り値に成功失敗を渡すので、本格的にプログラム開発をするならば各関数についてそれぞれエラーチェックを行わなければならないのですが、まあさすがにそこまではいいでしょう。
カウンターひとつでいろいろと面倒ですね。
動かすだけならもっと適当でもいいのですが、不用意なバグを防ぐためにも丁寧な書き方をお勧めします。
まあ、人のことは言えませんが。