PHPでアクセスカウンタを作る

(1/1)
アクセスカウンタ
無料のアクセスカウンタは数多くあるが、中にはアクセスログを横取りする悪質なカウンタもある。PHPが使えるなら、自分自身のサイトにカウンタを置いておきたい。
ここでは、最も基本的なカウンタ・プログラムの作り方を紹介する。
(2023年6月11日)ファイルオープン・エラー時のリトライ処理を追加
(2023年4月15日)関数名変更,解説を増補した

サンプル・プログラム

圧縮ファイルの内容
counter.phpサンプル・プログラム本体。
counter.php 更新履歴
バージョン 更新日 内容
3.3.0 2023/06/11 ファイルオープン・エラー時のリトライ処理を追加
3.2.0 2021/03/06 関数名変更など
3.1 2021/03/06 PHP8対応
3.0 2015/05/19 大幅改訂
2.2 2008/08/20 カウンタ・ファイルがない場合の初期化処理を追加

目次

サンプル・プログラムの解説:準備

  10: // 初期化処理 ================================================================
  11: define('INTERNAL_ENCODING', 'UTF-8');
  12: mb_internal_encoding(INTERNAL_ENCODING);
  13: mb_regex_encoding(INTERNAL_ENCODING);
  14: define('REFERENCE', 'https://www.pahoo.org/e-soul/webtech/php02/php02-04-01.shtm');
  15: 
  16: //プログラム・タイトル
  17: define('TITLE', 'PHPでアクセスカウンタを作る');
  18: 
  19: //表示幅(ピクセル)
  20: define('WIDTH', 600);
  21: 
  22: //エラー時のリトライ回数
  23: define('RETRY', 5);
  24: 
  25: //カウンタを記録するファイル名;write enable属性を与えておくこと
  26: $CounterFile = './mycounter.txt';
  27: 
  28: /**
  29:  * 共通HTMLヘッダ
  30:  * @global string $HtmlHeader
  31: */
  32: $encode = INTERNAL_ENCODING;
  33: $title  = TITLE;
  34: $width  = WIDTH;
  35: $HtmlHeader =<<< EOT
  36: <!DOCTYPE html>
  37: <html lang="ja">
  38: <head>
  39: <meta charset="{$encode}" />
  40: <title>{$title}</title>
  41: <meta name="author" content="studio pahoo">
  42: <meta name="copyright" content="studio pahoo">
  43: <meta name="ROBOTS" content="NOINDEX,NOFOLLOW">
  44: <meta http-equiv="pragma" content="no-cache">
  45: <meta http-equiv="cache-control" content="no-cache">
  46: <meta http-equiv="X-UA-Compatible" content="IE=edge">
  47: <meta name="viewport" content="width={$width},user-scalable=yes">
  48: </head>
  49: 
  50: EOT;
  51: 
  52: /**
  53:  * 共通HTMLフッタ
  54:  * @global string $HtmlFooter
  55: */
  56: $HtmlFooter =<<< EOT
  57: </html>
  58: 
  59: EOT;
  60: 
  61: // サブルーチン ==============================================================

アクセス回数はテキストファイル $CounterFile に保持する。このプログラムが終了すると、変数の内容はメモリ上から消えてなくなってしまうためだ。
このように、プログラムが終了してもデータを残したい場合は、ファイルやデータベースに書き込む。

ソースコードは UTF-8 で記述しているので、念のため、組み込み関数  mb_internal_encoding  を使って宣言しておく。
プログラム・タイトルは定数 TITLE に、本ページのURLは定数 REFERENCE に定義しておく。これらは画面表示で参照する。

HTMLヘッダは変数 $HtmlHeader に用意しておく。
二重引用符による文字列記述は面倒なので、、ヒアドキュメントを利用する。
また、ページのキャッシングが行われてしまうとカウンタが増加したのが表示されないので、meta タグにキャッシング禁止指示 no-cache を指定している。

解説:カウンタを1つ加算する

  62: /**
  63:  * カウンタを1つ加算する.
  64:  * @param   string $fname カウンタ・ファイル名
  65:  * @return  int カウンタ値
  66: */
  67: function addCounter($fname) {
  68:     //カウンタ・ファイルがあるかどうか調べ、無ければ作成する.
  69:     if (! file_exists($fname)) {
  70:         $fp = @fopen($fname, 'w');
  71:         fputs($fp, '0');
  72:         fclose($fp);
  73:         chmod($fname, 0644);
  74:     }
  75: 
  76:     //カウンター・ファイルをオープンする(リトライ付)
  77:     for ($i = 0$i < RETRY$i++) {
  78:         $fp = @fopen($fname, 'r');
  79:         if ($fp !NULL)    break;
  80:     }
  81:     //直前のカウンタ値をカウンタ変数に読み込む.
  82:     $counter = (int)fgets($fp);
  83:     fclose($fp);
  84: 
  85:     //カウンタ変数を+1だけ増加させる.
  86:     $counter++;
  87: 
  88:     //カウンター・ファイルをオープンする(リトライ付)
  89:     for ($i = 0$i < RETRY$i++) {
  90:         $fp = @fopen($fname, 'w');
  91:         if ($fp !NULL)    break;
  92:     }
  93:     //カウンタ変数をカウンタ・ファイルに書き込む.
  94:     flock($fp, LOCK_EX);            //ロックをかける(排他制御)
  95:     $s = sprintf('%d', $counter);
  96:     fputs($fp, $s);
  97:     flock($fp, LOCK_UN);            //ロックを解除する(排他制御)
  98:     fclose($fp);
  99: 
 100:     return $counter;
 101: }

カウンタを1つ加算し、カウンタ・ファイルの内容を更新するのがユーザー関数 addCounter だ。

初めてこのプログラムを走らせるときには、カウンタ・ファイル $CounterFile は存在しない。
PHPスクリプトは、自分自身が初めて起動されたかどうかを知ることはできないので、関数  file_exists  を使い、$CounterFile が存在しているかどうか検査する。
存在していなければ、初期値 0 を代入したファイルを生成し、関数  chmod  によって所有者に書き込み権限を与える。

ファイルの内容を直接書き換えることは出来ない。
そこで、組み込み関数  fgets  を使ってカウンタ・ファイル $CounterFile の内容を変数 $counter に読み込む。このとき、読み込んだ内容が整数であることを明示的に示す(キャスティング)するために (int) と書く。
次に、変数 $counter をインクリメント(+1加算)し、組み込み関数  fputs  を使ってカウンタ・ファイル $CounterFile の内容を書き換える。

Webサービスでは、同時に複数のユーザーがサイトを閲覧することが有り得る。このとき、本プログラムも同時に複数が立ち上がる。一方、カウンタ・ファイル $CounterFile は1つしかない。
また、排他処理が働いているとき、他のスレッドが  fopen してくる可能性があるので、定数 RETRY だけ繰り返してオープンするようにした(リトライ処理)。
排他制御
同時に複数のプログラムが1つのカウンタ・ファイルを書き換えようとすると矛盾が起きるので、お店のレジに並んでもらうイメージで、ファイルの書き換えタスクは順番に処理しなければならない。これを排他制御と呼ぶ。
そこで、書き換えている最中(レジで処理している最中)は、関数  flock  によって、他のプログラムがファイルを書き換えないようロックする。

解説:アクセス回数の表示

 103: /**
 104:  * HTML BODYを作成する.
 105:  * @param   int $counter カウンタ
 106:  * @return  string HTML BODY
 107: */
 108: function makeCommonBody($counter) {
 109:     $refere = REFERENCE;
 110:     $width  = WIDTH;
 111:     $title = TITLE;
 112:     $version = '<span style="font-size:small;">' . date('Y/m/d版', filemtime(__FILE__)) . '</span>';
 113: 
 114:     $body =<<< EOT
 115: <body>
 116: <h2>{$title} {$version}</h2>
 117: <p>
 118: あなたは {$counter} 人目の訪問者です
 119: </p>
 120: <div style="border-style:solid; border-width:1px; margin:20px 0px 0px 0px; padding:5px; width:{$width}px; font-size:small; overflow-wrap:break-word; word-break:break-all;">
 121: ※参考サイト:<a href="{$refere}">{$refere}</a>
 122: </div>
 123: </body>
 124: 
 125: EOT;
 126:     return $body;
 127: }

アクセス回数の表示は、ユーザー関数 makeCommonBody で行う。

参考サイト

(この項おわり)
header