[Utility.php] 携帯電話を判別する。IPアドレスから。【確定版】

昨日、[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\">&nbsp;&nbsp;([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関連記事プラグインによって表示されています。

Yahoo!ブックマーク Googleブックマーク はてなブックマーク livedoorClip del.icio.us newsing FC2 Technorati ニフティクリップ iza Choix Flog Buzzurl 

1 user responded in this post

Subscribe to this post comment rss or trackback url
User Gravatar
すずき said in 10月 9th, 2009 at 6:01 PM

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

Leave A Reply

 Username (Required)

 Email Address (Remains Private)

 Website (Optional)

Yahoo!ブックマーク Googleブックマーク はてなブックマーク livedoorClip del.icio.us newsing FC2 Technorati ニフティクリップ iza Choix Flog Buzzurl 

(株)オルタ

香川県高松市。

ホームページ制作
・ショッピングサイト制作
・コミュニティサイト制作
・システム開発
・各種業務アプリケーション
・仕様作成、設計、開発、運用、保守...

カレンダー

2008年9月
« 8月   3月 »
 123456
78910111213
14151617181920
21222324252627
282930  

検索

カスタム検索

自己紹介

aulta (オルタ)
香川県高松市在住。
1979年2月生の男。
- - - - - - - - - - - -
PHP, Perl, JAVA, Tomcat, MySql, Oracle, Sql Server, Linux, html, xml, css, javascript, VB, VC, etc...
- - - - - - - - - - - -
香川でプログラムやweb関係の方と仲良くなりたいと思ってます。あっでも地域も趣味も幅広く色んな人と仲良くなりたいので、私に興味を持たれた方はお気軽に【ご連絡】ください。