ユーザのブラウザの種類を判別する情報として、以下のようなユーザエージェント名が取得できるが、これを見ても知らなければ詳しい情報がわからない。

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Sleipnir/2.6.1
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/523.12.2 (KHTML, like Gecko) Version/3.0.4 Safari/523.12.2
Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
Opera/9.50 (X11; Linux i686; U; en)
Hatena RSS/0.3 (http://r.hatena.ne.jp; 1 subscribers)

PHPにはこういったユーザエージェント名から詳しい情報を取得するget_browser関数がある。 しかし、これは標準のPHPの設定では使うことができずiniファイルの設定が必要になる。 そこで、get_browser関数の使い方とiniファイルが書きかえれない場合の対処方法を紹介する。


get_browser関数の使い方

まずBrowser Capabilities ProjectDownloadsからphp_browscap.iniをDLする。 browscap.iniではないことに注意。 軽量版のlite_php_browscap.iniでもOK。

(※Browser Capabilities Projectで公開されているユーザエージェント情報のファイルはCreative Commons Attribution-NonCommercial Licenseの下で公開されているため商用目的には使えないことに注意。)

次にphp.iniまたはhttpd.confで、browscapにそのDLしたファイルのパスを設定する。

[browscap]
browscap = /etc/php5/php_browscap.ini

設定後、apacheを再起動すると、get_browser関数が使えるようになる。

例えば、以下のようにするとアクセスした人のユーザエージェント情報を表示する。

<?php
print_r(get_browser());
?>

特定のユーザエージェントの情報がほしい場合にはユーザエージェント名を第一引数に渡す。

<?php
print_r(get_browser('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'));
?>
stdClass Object
(
	[browser_name_regex] => ^mozilla/4\.0 (compatible; msie 6\.0; .*windows nt 5\.1.*).*$
	[browser_name_pattern] => Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1*)*
	[parent] => IE 6.0
	[platform] => WinXP
	[browser] => IE
	[version] => 6.0
	[majorver] => 6
	[win32] => 1
	[frames] => 1
	[iframes] => 1
	[tables] => 1
	[cookies] => 1
	[backgroundsounds] => 1
	[cdf] => 1
	[vbscript] => 1
	[javaapplets] => 1
	[javascript] => 1
	[activexcontrols] => 1
	[css] => 2
	[cssversion] => 2
	[supportscss] => 1
	[minorver] => 0
	[alpha] => 
	[beta] => 
	[win16] => 
	[win64] => 
	[authenticodeupdate] => 
	[stripper] => 
	[isbanned] => 
	[wap] => 
	[ismobiledevice] => 
	[issyndicationreader] => 
	[crawler] => 
	[aol] => 
	[aolversion] => 0
	[netclr] => 
	[clrversion] => 0
)

get_browser関数の返す値は通常オブジェクトなので、配列で返してほしければ第二引数にtrueを渡す。 このときアクセスした人のユーザエージェント情報を取得したければ第一引数にnullを渡す。

<?php
print_r(get_browser(null, true));
?>

自分のブラウザではどうなるのか見たい場合はBrowser Capabilities Projectのメニューの「Your Browser」から、あるユーザエージェントの場合を見たい場合は「User Agent Checker」から見ることができる。

get_browser関数を使う上での問題点

get_browser関数は簡単に使えて便利なのだが、いくつかの問題点がある。

  • browscap.iniは商用目的に使えない。
  • browscapの設定項目はPHP_INI_SYSTEMであるため、php.ini または httpd.confでしか設定できない。
  • 新しいユーザエージェントに対応するためにはbrowscap.iniを更新しなければならない。
  • browscap.ini更新後はapacheの再起動が必要。(一部のサーバでは必要なし。)

商用目的に使えないというのはいいとして、残りの問題点がやっかいだ。 管理者が導入していない限り使うことができず、使えたとしても管理者が更新していなければ古いデータでしか使うことができない。 また情報に誤りがあって修正したい場合にも管理者権限が必要になる。

実際、これを書いている時点でおそらく誤りだと思われる箇所が一つ見つかった。

browscap.iniの6470行目。

[Motorola Web Browser]
Parent=DefaultProperties
Browser="Motorola Internet Browser"
Frames=true
Tables=true
Cookies=true
WAP=true
isMobileDevice=true

上記の"[Motorola Web Browser]"は、「Motorola Internet Browser」を親としているユーザエージェントが親の情報を取得できていないことから、おそらく"[Motorola Internet Browser]"が正しいと思われる。

[Motorola Internet Browser]
Parent=DefaultProperties
Browser="Motorola Internet Browser"
Frames=true
Tables=true
Cookies=true
WAP=true
isMobileDevice=true
iniファイルが変更できない場合の対処法

管理者権限がなく、browscapも設定されていないため、get_browser関数を使うのを諦めてしまう人もいるだろう。 そこでphp_browscap.iniを使ってget_browser関数と同じ働きをする関数を作ってみる。

まずphp_browscap.iniの中を説明する。 php_browscap.iniの中身は以下のようになっている。

[Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0*)*]
Parent=IE 7.0
Platform=WinVista

[Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0;*.NET CLR 1*)*]
Parent=IE 7.0
Platform=WinVista
netCLR=true
ClrVersion=1

[]で挟まれた部分がワイルドカードを使って表したユーザエージェント名である。 その下がそのユーザエージェントの情報を表している。 Parentがある場合、その値と同じ名前を持つ項目の情報も持つ。 上の2つのユーザエージェントの場合、「IE 7.0」という項目の情報をそれぞれ持つということになる。

[IE 7.0]
Parent=DefaultProperties
Browser="IE"
Version=7.0
MajorVer=7
Win32=true
Frames=true
IFrames=true
Tables=true
Cookies=true
BackgroundSounds=true
CDF=true
VBScript=true
JavaApplets=true
JavaScript=true
ActiveXControls=true
CSS=2
CssVersion=2
supportsCSS=true

さらに、「IE 7.0」は「DefaultProperties」をの情報を持つ。

[DefaultProperties]
Browser="DefaultProperties"
Version=0
MajorVer=0
MinorVer=0
Platform=unknown
Alpha=false
Beta=false
Win16=false
Win32=false
Win64=false
Frames=false
IFrames=false
Tables=false
Cookies=false
BackgroundSounds=false
AuthenticodeUpdate=
CDF=false
VBScript=false
JavaApplets=false
JavaScript=false
ActiveXControls=false
Stripper=false
isBanned=false
WAP=false
isMobileDevice=false
isSyndicationReader=false
Crawler=false
CSS=0
CssVersion=0
supportsCSS=false
AOL=false
aolVersion=0
netCLR=false
ClrVersion=0

同じ名前のデータは親の値が子の値で上書きされる。例えば、"[Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0*)*]"をget_browser関数で取得すると以下のようになる。

Array
(
	[browser_name_regex] => ^mozilla/4\.0 (compatible; msie 7\.0; .*windows nt 6\.0.*).*$
	[browser_name_pattern] => Mozilla/4.0 (compatible; MSIE 7.0; *Windows NT 6.0*)*
	[parent] => IE 7.0
	[platform] => WinVista
	[browser] => IE
	[version] => 7.0
	[majorver] => 7
	[win32] => 1
	[frames] => 1
	[iframes] => 1
	[tables] => 1
	[cookies] => 1
	[backgroundsounds] => 1
	[cdf] => 1
	[vbscript] => 1
	[javaapplets] => 1
	[javascript] => 1
	[activexcontrols] => 1
	[css] => 2
	[cssversion] => 2
	[supportscss] => 1
	[minorver] => 0
	[alpha] => 
	[beta] => 
	[win16] => 
	[win64] => 
	[authenticodeupdate] => 
	[stripper] => 
	[isbanned] => 
	[wap] => 
	[ismobiledevice] => 
	[issyndicationreader] => 
	[crawler] => 
	[aol] => 
	[aolversion] => 0
	[netclr] => 
	[clrversion] => 0
)

このようにphp_browscap.iniからユーザエージェントの情報を取り出すわけだが、これだけでは少し問題がある。 例えば、"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)"というユーザエージェントでは以下の5つにマッチする。

Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1*)*
Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1;*.NET CLR 1*)*
Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1;*.NET CLR 1*.NET CLR 2*)*
Mozilla/4.0 (compatible; MSIE 6.0; *Windows NT 5.1;*.NET CLR 2*)*
*

正解は3つ目で、get_browser関数でもこの情報が取得される。 上の5つはファイルに書かれている順で、単純に最初にマッチしたものというわけにはいかないため、5つの中からもっとも近い1つを選択する必要がある。 これは単純にマッチしたパターンの中で一番長い文字列を選択しても良さそうだが、文字列の類似度を計算した方が確実そうなのでこれを使うことにする。

(※文字列の類似度計算については似た文字列を探す [PHP, Tips]参照。)

以上よりget_browser関数と同じ働きをする関数を作ってみると以下のようになる。

<?php
function get_browser_info($user_agent = null, $ini_path = null){
	//引数が省略された場合のデフォルト値を設定
	if($user_agent === null)
		$user_agent = $_SERVER['HTTP_USER_AGENT'];
	if($ini_path === null)
		$ini_path = ini_get('browscap');

	//iniファイルからデータを取得
	$browser_info_list = parse_ini_file($ini_path, true);

	//マッチするパターンを探す
	foreach($browser_info_list as $pattern => $browser_info){
		if(fnmatch($pattern, $user_agent, FNM_CASEFOLD))
			$match_pattern_list[] = $pattern;
	}

	//もっともマッチするパターンを選択
	$best_score = PHP_INT_MAX;
	foreach($match_pattern_list as $pattern){
		$score = levenshtein($user_agent, $pattern);
		if($best_score > $score){
			$best_score = $score;
			$best_pattern = $pattern;
		}
	}

	//情報を取得
	$result = array();
	$regex = preg_replace(array("/\./","/\*/","/\?/"), array("\.",".*","."), $best_pattern);
	$result['browser_name_regex'] = strtolower("^{$regex}$");
	$result['browser_name_pattern'] = $best_pattern;
	$result += $browser_info_list[$best_pattern];

	//親を辿って、親の情報を取得
	$parent = @$result['Parent'];
	while($parent){
		$result += (array)$browser_info_list[$parent];
		$parent = @$browser_info_list[$parent]['Parent'];
	}

	//キーを全て小文字に変換
	$r = array();
	foreach($result as $key => $value){
		$r[strtolower($key)] = $value;
	}

	return $r;
}
?>

ちなみに、この関数はパターンのマッチングにfnmatch関数を使っているためWindowsサーバでは動かない。 また、オリジナルのget_browser関数と3000件ほどのデータで速度の比較をしたところおよそ50秒と150秒で3倍ほど遅かった。