はじめに
PHPのlong2ip
関数は、32ビットの整数値をIPアドレス文字列に変換する関数です。ネットワークプログラミングやIPアドレスの操作において重要な役割を果たします。
今回は、long2ip
関数の基本的な使い方から応用例まで、詳しく解説していきます。
long2ip関数とは
基本構文
string long2ip(int $ip)
パラメータ:
$ip
:32ビット符号付き整数のIPアドレス
戻り値:
- IPアドレスを表す文字列(例:「192.168.1.1」)
基本的な使用例
// 整数値をIPアドレスに変換
$ip_long = 3232235777;
$ip_string = long2ip($ip_long);
echo $ip_string; // 出力:192.168.1.1
// 別の例
$ip_long = 16777343;
$ip_string = long2ip($ip_long);
echo $ip_string; // 出力:1.0.0.127
IPアドレスの内部表現
なぜ整数で表現するのか
IPアドレスは4つのオクテット(8ビット)で構成されており、合計32ビットです。これを整数として扱うことで、以下のメリットがあります:
- 計算が高速:IPアドレス範囲の判定や演算が効率的
- データベース保存:文字列より少ないストレージ容量
- ソート処理:数値として自然な順序でソート可能
IPアドレスと整数の変換方法
// IPアドレスの各オクテットを分解
function explainIpConversion($ip_string) {
$parts = explode('.', $ip_string);
echo "IPアドレス: {$ip_string}\n";
echo "オクテット分解:\n";
$total = 0;
for ($i = 0; $i < 4; $i++) {
$value = intval($parts[$i]) * pow(256, 3 - $i);
$total += $value;
echo " {$parts[$i]} × 256^" . (3-$i) . " = {$value}\n";
}
echo "合計: {$total}\n";
echo "long2ip({$total}) = " . long2ip($total) . "\n";
}
// 実行例
explainIpConversion('192.168.1.1');
出力結果:
IPアドレス: 192.168.1.1
オクテット分解:
192 × 256^3 = 3221225472
168 × 256^2 = 11010048
1 × 256^1 = 256
1 × 256^0 = 1
合計: 3232235777
long2ip(3232235777) = 192.168.1.1
ip2longとの組み合わせ
相互変換
long2ip
はip2long
の逆の処理を行います:
$ip_string = '192.168.1.1';
// 文字列 → 整数
$ip_long = ip2long($ip_string);
echo "ip2long('{$ip_string}') = {$ip_long}\n";
// 整数 → 文字列
$ip_back = long2ip($ip_long);
echo "long2ip({$ip_long}) = '{$ip_back}'\n";
// 検証
var_dump($ip_string === $ip_back); // bool(true)
32ビット符号の注意点
32ビットシステムでは、大きなIPアドレスで符号の問題が発生する可能性があります:
// 符号なし整数として扱う場合
function safeIp2long($ip) {
$long = ip2long($ip);
if ($long === false) {
return false;
}
// 32ビットシステムでの符号問題を回避
if ($long < 0) {
$long += 4294967296; // 2^32を加算
}
return $long;
}
function safeLong2ip($long) {
// 符号なし32ビット整数の範囲をチェック
if ($long > 4294967295) {
$long -= 4294967296;
}
return long2ip($long);
}
// 使用例
$ip = '255.255.255.255';
$long = safeIp2long($ip);
$back = safeLong2ip($long);
echo "{$ip} → {$long} → {$back}\n";
実用的な応用例
1. IPアドレス範囲の判定
class IPRangeChecker {
private $start_ip;
private $end_ip;
public function __construct($start, $end) {
$this->start_ip = ip2long($start);
$this->end_ip = ip2long($end);
}
public function isInRange($ip) {
$ip_long = ip2long($ip);
return ($ip_long >= $this->start_ip && $ip_long <= $this->end_ip);
}
public function getIpList($limit = 10) {
$ips = [];
$count = min($limit, $this->end_ip - $this->start_ip + 1);
for ($i = 0; $i < $count; $i++) {
$ips[] = long2ip($this->start_ip + $i);
}
return $ips;
}
}
// 使用例
$ranger = new IPRangeChecker('192.168.1.1', '192.168.1.10');
// 範囲チェック
var_dump($ranger->isInRange('192.168.1.5')); // bool(true)
var_dump($ranger->isInRange('192.168.2.1')); // bool(false)
// IP一覧の取得
$ip_list = $ranger->getIpList();
print_r($ip_list);
2. サブネット計算
class SubnetCalculator {
public static function getNetworkInfo($ip, $cidr) {
$ip_long = ip2long($ip);
$mask_long = -1 << (32 - $cidr);
$network_long = $ip_long & $mask_long;
$broadcast_long = $network_long | (~$mask_long);
return [
'network' => long2ip($network_long),
'broadcast' => long2ip($broadcast_long),
'subnet_mask' => long2ip($mask_long),
'first_host' => long2ip($network_long + 1),
'last_host' => long2ip($broadcast_long - 1),
'total_hosts' => pow(2, 32 - $cidr) - 2
];
}
}
// 使用例
$subnet_info = SubnetCalculator::getNetworkInfo('192.168.1.100', 24);
print_r($subnet_info);
出力結果:
Array
(
[network] => 192.168.1.0
[broadcast] => 192.168.1.255
[subnet_mask] => 255.255.255.0
[first_host] => 192.168.1.1
[last_host] => 192.168.1.254
[total_hosts] => 254
)
3. データベースでのIP管理
class IPLogger {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function logAccess($ip, $user_agent) {
$ip_long = ip2long($ip);
$stmt = $this->pdo->prepare(
"INSERT INTO access_logs (ip_long, ip_string, user_agent, created_at)
VALUES (?, ?, ?, NOW())"
);
return $stmt->execute([$ip_long, $ip, $user_agent]);
}
public function getAccessesByRange($start_ip, $end_ip) {
$start_long = ip2long($start_ip);
$end_long = ip2long($end_ip);
$stmt = $this->pdo->prepare(
"SELECT ip_long, user_agent, created_at
FROM access_logs
WHERE ip_long BETWEEN ? AND ?
ORDER BY created_at DESC"
);
$stmt->execute([$start_long, $end_long]);
$results = $stmt->fetchAll();
// IPアドレスを文字列に変換
foreach ($results as &$row) {
$row['ip_string'] = long2ip($row['ip_long']);
}
return $results;
}
}
// テーブル作成例
$create_table = "
CREATE TABLE access_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
ip_long INT UNSIGNED,
ip_string VARCHAR(15),
user_agent TEXT,
created_at DATETIME,
INDEX idx_ip_long (ip_long),
INDEX idx_created_at (created_at)
)";
4. IPアドレスのソートとフィルタリング
function sortIpAddresses($ip_array) {
// IPアドレスを整数値でソート
usort($ip_array, function($a, $b) {
return ip2long($a) <=> ip2long($b);
});
return $ip_array;
}
function filterPrivateIPs($ip_array) {
$private_ranges = [
['10.0.0.0', '10.255.255.255'],
['172.16.0.0', '172.31.255.255'],
['192.168.0.0', '192.168.255.255']
];
return array_filter($ip_array, function($ip) use ($private_ranges) {
$ip_long = ip2long($ip);
foreach ($private_ranges as $range) {
$start = ip2long($range[0]);
$end = ip2long($range[1]);
if ($ip_long >= $start && $ip_long <= $end) {
return true;
}
}
return false;
});
}
// 使用例
$ips = ['192.168.1.10', '8.8.8.8', '172.16.0.1', '192.168.1.1', '1.1.1.1'];
echo "元のリスト:\n";
print_r($ips);
echo "\nソート後:\n";
$sorted = sortIpAddresses($ips);
print_r($sorted);
echo "\nプライベートIPのみ:\n";
$private = filterPrivateIPs($ips);
print_r($private);
エラーハンドリングとバリデーション
入力値の検証
function validateAndConvert($input) {
// 整数値の場合
if (is_numeric($input)) {
$ip_long = intval($input);
// 32ビット符号なし整数の範囲チェック
if ($ip_long < 0 || $ip_long > 4294967295) {
throw new InvalidArgumentException("IP値が範囲外です: {$input}");
}
$result = long2ip($ip_long);
if ($result === false) {
throw new InvalidArgumentException("無効なIP値です: {$input}");
}
return $result;
}
// 文字列の場合はそのまま返す(別途検証が必要)
if (filter_var($input, FILTER_VALIDATE_IP)) {
return $input;
}
throw new InvalidArgumentException("無効な入力です: {$input}");
}
// 使用例
try {
echo validateAndConvert(3232235777) . "\n"; // 192.168.1.1
echo validateAndConvert('8.8.8.8') . "\n"; // 8.8.8.8
echo validateAndConvert(-1) . "\n"; // 例外
} catch (InvalidArgumentException $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
パフォーマンス比較
文字列処理 vs 整数処理
function benchmarkIpOperations() {
$ips = [];
// テストデータ生成
for ($i = 0; $i < 10000; $i++) {
$ips[] = long2ip(rand(16777216, 3758096383)); // 1.0.0.0 - 224.0.0.255
}
// 文字列での範囲チェック
$start_time = microtime(true);
$count1 = 0;
foreach ($ips as $ip) {
$parts = explode('.', $ip);
if ($parts[0] >= 192 && $parts[0] <= 223) {
$count1++;
}
}
$time1 = microtime(true) - $start_time;
// 整数での範囲チェック
$start_time = microtime(true);
$count2 = 0;
$start_range = ip2long('192.0.0.0');
$end_range = ip2long('223.255.255.255');
foreach ($ips as $ip) {
$ip_long = ip2long($ip);
if ($ip_long >= $start_range && $ip_long <= $end_range) {
$count2++;
}
}
$time2 = microtime(true) - $start_time;
echo "文字列処理: {$time1}秒 (結果: {$count1})\n";
echo "整数処理: {$time2}秒 (結果: {$count2})\n";
echo "速度向上: " . round($time1 / $time2, 2) . "倍\n";
}
benchmarkIpOperations();
まとめ
long2ip
関数は、PHPでIPアドレスを効率的に扱うための重要な関数です。
主要なポイント:
- 32ビット整数をIPアドレス文字列に変換
ip2long
と組み合わせて相互変換が可能- IPアドレスの範囲判定や計算が高速
- データベースでの保存効率が良い
- ネットワーク計算に適している
注意点:
- 32ビットシステムでの符号の問題
- 入力値の妥当性検証が重要
- IPv4のみ対応(IPv6は別の関数が必要)
適切に活用することで、ネットワーク関連の処理を効率的に実装できます。