rm /blog

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【PHP】簡単なアクセスカウンタをつくろう

PHPで簡単なアクセスカウンターを作る!コーナー


 

 
WEBページにアクセスカウンター付けるの、一昔前流行ありましたよね。
(全盛期は、もう15~20年くらい前?かな)
最近ではブログの付随パーツとかで、
マウスオーバーでPVとUUのグラフが出るリッチなカウンタとかありますけど、
簡単なアクセスカウンタなら自作ができるわけです。
いろいろなやり方があるらしいのだが、
ここではPHPで簡単なアクセスカウンターを作ってみます。
(まあググれば出てくるんだけど自分の学習用に。。。)




仕様としては

  • PV数をアクセス数としてカウントする
  • トップページ(indexページ)を含め、サイト内全ページをカウント対象とする
  • トップページでのみ、アクセス状況を表示する(総数/当日/前日)

といったものです。
各ページに「カウントする」という処理だけを埋め込んでおくことで実現させます。
(逆に言うと意図的に埋め込まなければカウント対象外となる)
なお、「PV数」と書いているのは、「UU数を使うつもりはない」というのを言いたいだけで、それ以外の意味はありません
…というより「UU数」の判断がちょっと面倒だから、というのを言い換えただけなんですが(^^;
上述した通り、トップページでは「カウントする」に加えて「現在値を表示する」という2つの処理を実装します。
要するに「カウントする」というのと「現在値を表示する」という2種類の処理が実装できればそれで事足りるわけですな。

カウンターの記録に関しては、一番手軽にできるテキストファイルの管理で進めます。
「総数ファイル」と「日別ファイル」の2種類を作り、
中身は単純にアクセス数が書いてあるだけのテキストファイルです。
ここから数値を取り出したり、逆に書き込んだりして、アクセス数の記録を行います。

■カウントアップ処理

PVする度、

  1. ファイルを開いて(なければ作って0とみなし)
  2. ファイル内の数値を取り出し
  3. 数値を+1して書き戻す

...という処理を実装します。
「総数ファイル」はともかく、当日最初のアクセスの場合、「当日ファイル」は存在しないため、
カウントアップに際しては、”カウントアップされる対象のファイルが存在しない”ことも見越しておかないといけません。
このため上記1.のような実装考慮が必要になります。
なお、カウントアップするのはあくまで「総数ファイル」「当日ファイル」のみであり、
「前日ファイル」はカウントアップとしては対象外(表示するときに使うのみ)です。

■アクセス数表示処理

前提として、まず、↑の「カウントアップ処理」の後にこの処理を動かします。
そうでないと、当日最初のアクセスが「アクセス数0」として表示されてしまうためです。
「カウントアップ処理」側に、「ファイルが存在しなければ作る」という考慮が入っているため、
基本的にこの「アクセス数表示処理」の段階ではファイルが存在していないということがあり得ません。

が、(悲しいことですが)前日1回もアクセスがなかった場合、
「前日ファイル」だけは作成されていない(前日ファイルが存在しない)可能性がある為、
やっぱり結局”ファイルがあったら中身を取り出し、ファイルがなければ0として振る舞う”ような考慮が必要になります。

以上をもとにして簡単にプログラミングしてみると以下のようなもんになります。

<?php
header("Content-type: application/x-javascript");
// GETパラメータから動作モードを取得
$mode = $_GET['MODE'];

// 総数ファイル、当日ファイル、前日ファイルを設定
$base_path = '.\\counter\\';
$date_all_file = $base_path.'all.txt';
$date_today_file = $base_path.date('Ymd').'.txt';
$date_yesterday_file = $base_path.date('Ymd',strtotime('-1 day')).'.txt';

// 総数ファイル、当日ファイルをカウントアップ
countUp($date_all_file);
countUp($date_today_file);

// 動作モード='1'の場合、アクセス数を画面表示
if ($mode == 1) {

    $count_all = getCount($date_all_file);
    $count_today = getCount($date_today_file);
    $count_yesterday = getCount($date_yesterday_file);
    
    echo "document.write('ALL       COUNT:".$count_all."<br/>');";
    echo "document.write('TODAY     COUNT:".$count_today."<br/>');";
    echo "document.write('YESTERDAY COUNT:".$count_yesterday."<br/>');";

}

// 引数で与えられたファイルの内容を取り出し、カウントアップして書き戻す
function countUp($filepath) {
    if (file_exists($filepath)) {
        $fp = fopen($filepath,'r+');
        if(flock($fp, LOCK_EX)){
            $current_count = fgets($fp,4096);
            $next_count = $current_count + 1;
            fseek($fp,0);
            fputs($fp,$next_count);
            flock($fp, LOCK_UN);
            fclose($fp);
        }
    } else {
        file_put_contents($filepath,'1');
    }
}

// 引数で与えられたファイルの内容を取り出し、返却する
function getCount($filepath) {
    $count = 0;
    if(file_exists($filepath)) {
        $fp = fopen($filepath,'r');
        $count = fgets($fp);
        fclose($fp);
    }
    
    return $count;

}

?>


で、これを「呼び出す」側、つまりサイト内の各ページ(アクセス数カウントする対象のページ)では、

...(略)...
<script type="text/javascript" src="./counter.php"></script>
...(略)...


という記述を仕込んでおきます。
また、トップページ(現在アクセス数を表示するページ)では、カウントアップに加えて表示も行いますので、

...(略)...
<script type="text/javascript" src="./counter.php?MODE=1"></script>
...(略)...

というように、呼び出す際にGETパラメータを付与しておきます。
現在アクセス数表示は、最終的に、PHPがechoで結果を返してくる形でそれを実現するため、
このコードを仕込んだ位置にアクセス数が表示される形になります。
GETパラメータなしの(カウントアップだけが目的の)処理なら極論ページ内のどこに仕込んでも問題ありませんが
アクセス数表示する場合=GETパラメータありの場合は表示したい箇所にこのコードを仕込む必要があります。

以下、使ってる関数等のちょっとした補足です。↓

  • PHPでのファイルオープンは、「fopen」という関数を使って行います。
    fopen関数にファイルパスを指定して、ポインタを取得して、以後のファイル操作系の関数に引数で渡していくことになります。
    第二引数はモード指定であり、"r+"は読み書き用、"r"は読み取り専用です。この辺は公式マニュアルが詳しいです。
  • ファイルの内容の「取り出し」は、「fgets」関数で行います。
    第一引数はfopen関数で取得したポインタで、第二引数は読み込むバイト量になっています。
  • 一度fgetsすると、ポインタが示すファイルの読み込み位置が移動してしまいます。これを任意の位置に操作するのが「fseek」関数です。
    第一引数はfopen関数で取得したポインタで、第二引数は移動後の位置を示します。「0」だとファイルの先頭になります。
  • ファイルに内容を書き出すのは「fputs」関数で行います。
    このとき、↑の「fseek」関数を使っていないと、読み切った後の位置に書込みしようとするので、
    例えば「12」って書かれたファイルを読み込んで12+1=13にした後、fseekせずにfputsすると、「1213」になってしまうのです。
  • 「fclose」でファイルを閉じます。引数にはfopenで取得したポインタを渡します。
  • flockでファイルへのアクセスを排他します。これもやっぱり公式マニュアルが詳しいです。
    非常にアクセス数の多いサイトだった場合、別の人が同時刻にアクセスしてくることが考えられますが、
    ファイルに排他をかけないと、同時刻の別の人のアクセスが正しくカウントされなくなってしまいます。
    このため、書き込むファイルを排他しておく必要があるのです(まあ俺のサイトはそんな考慮必要なほどアクセス数ないですが…)
  • 「file_exists」関数は、引数のファイルパスにファイルが存在するかどうかをチェックします。
    ファイルがあるなら読み書きすればいいのですが、存在しない場合、「当日アクセス」は1/「前日アクセス」は0にする等を分岐します。
  • 「file_puts_contents」は、第一引数のファイルパスのファイルに第二引数の文字を書き込む関数です。
    fopenとかfseekとかfputsとかを包括してまとめたような関数で、直感的に分かりやすい関数になっています。
    排他とか追記とかの設定も第三引数以降で指定できるようですがやっぱり公式マニュアルが詳しいです。
  • GETパラメータでMODE=1を与えられた場合、アクセス数表示として動作させます。
    総数・当日・前日の各アクセス数ファイルを読み取り、javascriptのdocument.writeで「表示」を実現します。
    このdocument.writeをjavascriptとして動作させるため、冒頭にheader関数でHTTPヘッダを設定しています。


なお、ファイルの読み書きを実行する為、
アクセス数ファイルの置き場となる、このPHPの同階層に置く「counter」ディレクトリには、
少なからず書込み(write)の権限が必要になります。
また、各々のアクセス数ファイルに関しても同様です。




ググれば同じ実装は出てきますが、自身の復習用に記事としてまとめました。
このように、ちょっとしたアクセスカウンタなら簡単に実装が出来ます。
といいつつ、アクセス数を外だしのファイルで管理しており、
カウントアップや表示の度にファイルIOが発生する点は、
処理性能の観点からはあまり適さないでしょうし、
大規模なアクセス数を誇るサイトのアクセスカウンタとしては不向きでしょう。
私のサイトくらいの、小規模でアクセスもそこまでないサイトに使うのが、
実運用上だと関の山だと思います。
まあ、「アクセスカウンタ」の設計と実装を理解する勉強用の素材ということで、
本記事としてはここまでとしておきたいと思います。