昨日、[Utility.php] 携帯電話を判別する。IPアドレスから。 で、コードを掲載したのだけど、やっぱり気に入らない箇所があるので、書き直した。
昨日の記事と重複する内容は省略するので、昨日の記事と合わせてお読み下さい。
変更した箇所
- IPアドレス帯域を手動更新してたのを、各webサイトから自動取得するようにした
- 生成するPHPコードをスリム化した。(263行→188行に)
- テストコードを追加した。
- PHPコードを生成するコードをclass化した。(※)
(※) システムに組み込んで本格的に使う箇所じゃないので、わざわざclassにする必要もないと思う。ブログに掲載するために、public と private の関係(functionのスコープ)なんかを明示したかっただけなので。
下記のコードを実行するとこのように出力されます。
生成された【 isMobile 】関数を、お使いのシステムにコピーして使用します。
<?php
/*
* PHP5以上(PHP4未サポート)
*
* 携帯電話各社のサイトからIPアドレス帯を取得し、
* IPアドレス(リモートホスト)から携帯キャリアを判別する
* PHPコードを生成する。
*
* 作成者 : aulta
* http://blog.aulta.net/
*/
// 定数の宣言
define("C_LF", "\n"); // 改行文字
define("C_TAB1", " "); // タブ文字
define("C_TAB2", C_TAB1 . C_TAB1);
define("C_TAB3", C_TAB1 . C_TAB2);
define("C_TAB4", C_TAB2 . C_TAB2);
define("C_TAB5", C_TAB2 . C_TAB3);
define("C_TAB6", C_TAB3 . C_TAB3);
define("C_TAB7", C_TAB3 . C_TAB4);
/*
* IPアドレス帯域で携帯キャリアを判別するクラス
*/
class mobileIpRange
{
const cc_return_value_docomo = "docomo";
const cc_return_value_au = "au";
const cc_return_value_yahoo = "yahoo";
const cc_return_value_other = "other";
private $carriers = null;
/*
* コンストラクタ
*/
public function __construct()
{
// 携帯電話会社のIPアドレス公開ページの情報をセット
$this->carriers = array(
array(
"carrier" => self::cc_return_value_docomo
, "uri" => "http://www.nttdocomo.co.jp/service/imode/make/content/ip/"
, "pattern" => "/<li>([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2})<\/li>/"
)
,
array(
"carrier" => self::cc_return_value_au
, "uri" => "http://www.au.kddi.com/ezfactory/tec/spec/ezsava_ip.html"
, "pattern" => "/<td><div class=\"TableText\">([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})<\/div><\/td>\s+<td><div class=\"TableText\">(\/[0-9]{1,2})<\/div><\/td>/"
)
,
array(
"carrier" => self::cc_return_value_yahoo
, "uri" => "http://creation.mb.softbank.jp/web/web_ip.html"
, "pattern" => "/<td bgcolor=\"#eeeeee\"> ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2})<\/td>/"
)
);
}
/*
* デスクトラクタ
*/
public function __destruct(){}
/*
* PHPコードを取得する
*/
public function getPhpCode()
{
// IPアドレス帯域の配列
$arrCidr = array();
foreach ($this->carriers as $ca){
if (($arr = $this->getIpRangeFromUri($ca["carrier"], $ca["uri"], $ca["pattern"])) === false){
return 'load error : ' . $ca["carrier"] . ' site' . C_LF;
}
foreach ($arr as $ar){
array_push($arrCidr, $ar);
}
}
// IPアドレスの最小値で昇順にソート
$func = create_function('$va, $vb'
, ' $a = $va[0]; $b = $vb[0]; if ($a == $b) return 0; return ($a < $b) ? -1 : 1;');
usort($arrCidr, $func);
// IPアドレス範囲が連続する箇所を連結して配列を作り直す
$list = array();
$cnt = count($arrCidr);
$prev = array(0, 0, 'none', '0.0.0.0/32');
$j = 0;
for ($i = 0; $i < $cnt; $i++){
if (($prev[1] + 1) == $arrCidr[$i][0]){ // 直前の最後の番号に続いている
if ($prev[2] == $arrCidr[$i][2]){ // 同じキャリア
$j--;
$list[$j][1] = $arrCidr[$i][1];
$list[$j][3] .= ', ' . $arrCidr[$i][3];
} else {
$list[$j] = $arrCidr[$i];
}
} else {
$list[$j] = $arrCidr[$i];
}
$j++;
$prev = $arrCidr[$i];
}
$arrCidr = null;
// $list 配列の上限・下限の中央の添え字を取得する
$cnt = count($list);
$upper = $cnt - 1;
$harf = ($upper - ($upper % 2)) / 2;
// 出力用の変数に条件式をセット
return $this->getCode($list, $harf, 0, $upper, 0);
}
/*
* 携帯電話会社がIPアドレス帯を公開しているURIからリストを取得
*/
private function getIpRangeFromUri($carrier, $uri, $pattern){
$retArr = array();
$html = file_get_contents($uri);
$html = mb_convert_encoding($html, "utf-8", "utf-8,sjis,euc");
if (preg_match_all($pattern, $html, $regs)){
$cnt = count($regs[1]);
for ($i = 0; $i < $cnt; $i++){
$cidr = $regs[1][$i] . $regs[2][$i];
$arr = $this->getRangeAndValue($cidr, $carrier);
array_push($retArr, $arr);
}
return $retArr;
}
return false;
}
/*
* CIDRから、IPアドレスの上限・下限とその他を配列で返す
*/
private function getRangeAndValue($cidr, $carrier){
$buf = explode('/', $cidr);
$start = sprintf('%u', ip2long($buf[0]));
$end = $start;
if (isset($buf[1])){
$end += $this->ipRange($buf[1]) - 1;
}
return array(
(float)$start
, (float)$end
, $carrier
, $cidr
);
}
/*
* IPアドレスの範囲の個数を返す
*/
private function ipRange($range){
if ($range > 32){
return 0;
} else if ($range < 0){
return 0;
} else {
return pow(2, (32 - $range));
}
}
/*
* IPアドレスを二分探索する条件式を作る
*/
private function getCode(&$list, $harf, $lower, $upper, $kaisou, $elseif = false){
$ret = "";
// 階層に合わせて、行の先頭にタブ文字を挿入
$tab = C_TAB2;
for ($i = 0; $i < $kaisou; $i++){
$tab .= C_TAB1;
}
// 次の代入用にカウントアップしとく
$kaisou++;
// 条件式を作る
$ret .= ($elseif ? '' : $tab) . 'if ($ip >= ' . $list[$harf][0] . '){'
. ' // $list[' . $harf . ' to ' . $upper . ']' . C_LF;
$ret .= $tab . C_TAB1 . 'if ($ip <= ' . $list[$harf][1] . '){' . C_LF;
$ret .= $tab . C_TAB2 . 'return \'' . $list[$harf][2] . '\';'
. ' // $list[' . $harf . '] : ' . $list[$harf][3] . C_LF;
$harf2 = $harf + 1;
$n = $upper - $harf2;
if ($n > 0){
$n = ($n + ($n % 2)) / 2 + $harf2;
$ret .= $tab . C_TAB1 . '} else ';
$ret .= $this->getCode($list, $n, $harf2, $upper, $kaisou, true);
} else if ($n == 0){
$ret .= $tab . C_TAB1 . '} else if ($ip >= ' . $list[$harf2][0] . '){' . C_LF;
$ret .= $tab . C_TAB2 . 'if ($ip <= ' . $list[$harf2][1] . '){' . C_LF;
$ret .= $tab . C_TAB3 . 'return \'' . $list[$harf2][2] . '\';'
. ' // $list[' . $harf2 . '] : ' . $list[$harf2][3] . C_LF;
$ret .= $tab . C_TAB2 . '}' . C_LF;
}
$ret .= $tab . C_TAB1 . '}' . C_LF;
$harf2 = $harf - 1;
$n = $harf - $lower;
if ($n > 0){
$n = ($n - ($n % 2)) / 2 + $lower;
$ret .= $tab . '} else ';
$ret .= $this->getCode($list, $n, $lower, $harf2, $kaisou-1, true);
}
if ( ! $elseif) $ret .= $tab . '}' . C_LF;
return $ret;
}
}
// 実行
$mobileIpRange = new mobileIpRange();
?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<meta NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
<title>携帯電話のIPアドレス判別</title>
<style type="text/css">
<!--
body, textarea{
font-size : 12px;
line-height:135%;
font-family:"MS Pゴシック", sans-serif;
}
textarea{
width:600px;
height:500px;
}
-->
</style>
</head>
<body>
<h1>携帯電話のIPアドレス判別</h1>
<form>
<h2>テストコード</h2>
<textarea><?php
// array('期待する値', 'IPアドレス')
$arrTest = array(
array('other', '192.168.0.1')
, array('docomo', '210.153.84.50')
, array('au', '121.111.231.1')
, array('au', '121.111.231.10')
, array('au', '121.111.231.161')
, array('au', '121.111.231.168')
, array('yahoo', '123.108.237.243')
, array('yahoo', '123.108.236.115')
, array('yahoo', '123.108.237.31')
, array('docomo', '124.146.174.10')
, array('docomo', '124.146.174.123')
, array('docomo', '124.146.175.52')
, array('docomo', '124.146.175.48')
);
echo '期待する値と戻り値が異なれば赤色で表示';
echo '<ul>';
foreach ($arrTest as $arr){
$ret = isMobile($arr[1]);
echo '<li style="color:' . ($ret == $arr[0] ? 'gray' : 'red') . ';">'
. $arr[0] . ' : ' . $arr[1] . ' : ' . $ret . '</li>';
}
echo '</ul>';
?></textarea>
<h2>判別関数</h2>
<textarea>
/*
* IPアドレスから、携帯キャリアを判別する
* IPアドレス帯域は追加・削除されることがあります。
* 携帯電話各社の発表事項に注意してください。
* 生成日時 : <?php echo date("Y-m-d H:i:s") . "\n";?>
*/
function isMobile($ipAddress){
$ip = sprintf('%u', ip2long($ipAddress));
<?php echo $mobileIpRange->getPhpCode();?>
return '<?php echo mobileIpRange::cc_return_value_other;?>';
}
</textarea>
</form>
</body>
</html>
つぶやく
No related posts.
関連記事はYARPP関連記事プラグインによって表示されています。


Related Articles
1 user responded in this post
PearのNet_IPv4ライブラリのipInNetwork()メソッドを利用すると、指定したIP帯域内に含まれているかの判定が可能です。
http://pear.php.net/manual/ja/package.networking.net-ipv4.php
Leave A Reply