URLのクリック数を集計する

# **[Google Analytics](http://www.google.com/intl/ja_jp/analytics/)** を利用する

きっとこれが一番オーソドックスな方法でしょう。PHP関係ないですねｗ

備考: **[googleアナリティクスで簡単にクリックカウント出来るコード](http://fanblogs.jp/oands/archive/268/0)**

# PHP と MySQL を利用する

**「いやいや、俺はPHPコードをゴリゴリ書いてMySQL上で管理したいんだ！」** っていう人はこちらの方法で。今回はIPごとの集計はせず、単純に直近のIPをチェックして連続クリックを防止する仕様にしてみます。また、 `http://` または `https://` スキームから始まるURLに限定することにします。つまり、相対パスや絶対パスで記述された自サイト内のリンクは対象としないということです。

**「相対パスや絶対パスのリンクも取得したい！」** という場合、 **[こんなふうに](http://qiita.com/mpyw/items/85d1e23dc7c58f1014d1)** 結構面倒なソースを書かなければならなくなるので、今回は割愛します。そのときこそGoogle大先生に頼りましょう。

## データベースのスキーマを準備する

#### `analytics` テーブル

|名前|型|属性|
|:--:|:--:|:--:|
| url | varchar(2083) | PRIMARY KEY<br />NOT NULL|
| count | unsigned int | NOT NULL |
| last_ip | varchar(39) | NOT NULL |
| last_datetime | datetime | NOT NULL |

URLの最大長は Internet Explorer の実装に合わせて `2083` バイト、IPアドレスの最大長はIPv6の実装に合わせて `39` バイトとします。但し、プライマリキー最大長制限に対応するための **[手順](#comment-e95c20a6a9a71ffd696c)** を踏まなければならないようです。

## 集計用のクッションページを作る

**[INSERT ... ON DUPLICATE KEY UPDATE](http://dev.mysql.com/doc/refman/5.1/ja/insert-on-duplicate.html)** 構文が大活躍します。

```html+php:http://example.com/jump.php
<?php

function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

try {

    switch (true) {
        case !isset($_GET['url']):
            throw new RuntimeException('Missing url parameter');
        case !filter_var($_GET['url'], FILTER_VALIDATE_URL):
            throw new RuntimeException('Invalid url parameter');
    }
    $url = $_GET['url'];
    $pdo = new PDO(
        'mysql:dbname=analytics;host=localhost;charset=utf8',
        'root',
        '',
        array(
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        )
    );
    $stmt = $pdo->prepare(implode(' ', array(
        'INSERT INTO analytics VALUES (?, 1, ?, NOW()) ON DUPLICATE KEY UPDATE',
        implode(', ', array(
            'count = IF(last_ip <> VALUES(last_ip), count + 1, count)',
            'last_ip = IF(last_ip <> VALUES(last_ip), VALUES(last_ip), last_ip)',
            'last_datetime = IF(last_ip <> VALUES(last_ip), VALUES(last_datetime), last_datetime)',
        )),
    )));
    $stmt->execute(array($url, $_SERVER['REMOTE_ADDR']));

} catch (PDOException $e) {

    $error = 'Database error';

} catch (RuntimeException $e) {

    $error = $e->getMessage();

}

header('Content-Type: text/html; charset=utf-8', true, 400);
if (isset($url)) {
    header('Location: ' . $url);
}

?>
<!DOCTYPE html>
<html>
<head>
    <title>Redirecting...</title>
</head>
<body>
<?php if (isset($error)): ?>
    <p><?=h($error)?></p>
<?php endif; ?>
<?php if (isset($url)): ?>
    <p>リダイレクトしない場合は<a href="<?=h($url)?>">ここ</a>をクリック</p>
<?php endif; ?>
</body>
</html>
```

## URLリライタを登録する

以下のような関数を作成しておきます。

```php:PHP5.3の場合はcallableタイプヒンティングを削除してください
function register_href_rewriter(callable $callback) {
    $functions[0] = function ($m) use ($callback) {
        if (!$m[1]) {
            return $m[0];
        }
        $m[0] = htmlspecialchars_decode($m[0], ENT_QUOTES);
        $m[0] = $callback($m[0]);
        return htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8');
    };
    $functions[1] = function ($m) use ($functions) {
        return preg_replace_callback(
            '/\s++(?:(href)|[^=]++)=(?:"\K[^"]++(?=")|\'\K[^\']++(?=\')|\K[^\s]++)/i',
            $functions[0],
            $m[0]
        );
    };
    $functions[2] = function ($buffer) use ($functions) {
        return preg_replace_callback(
            '/<a\K[^>]++(?=>)/',
            $functions[1],
            $buffer
        );
    };
    return ob_start($functions[2]);
}
```

利用するときは、HTML出力を開始する前に以下のようにして、集計用のクッションページにジャンプさせるようにします。

```php
register_href_rewriter(function ($url) {
    return
         filter_var($url, FILTER_VALIDATE_URL) ?
         'http://example.com/jump.php?url=' . urlencode($url) :
         $url
    ;
});
```

　
(追記)

コメント欄で **[提案](#comment-5abce6ce394615ec80c5)** があったように、クッションページとしてではなくAjaxで登録＆更新専用スクリプトとして実装した方がいろいろメリットがありそうです。