我在数据库中有一个名为 ip
IP 地址在使用 PHP 函数转换后以 BINARY(16)
(无排序规则)形式存储在此列中
$store_ip = inet_pton($ip);
当我尝试插入相同的 IP 两次时,它工作正常,但由于它是唯一的而失败,
但是当我尝试选择 IP 时它不起作用并且总是返回 FALSE(未找到)
<?php
try {
$ip = inet_pton($_SERVER['REMOTE_ADDR']);
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=?");
$stmt->execute([$ip]);
$get = $stmt->fetch();
if( ! $get){
echo 'Not found';
}else{
echo 'Found';
}
// close connection
$get = null;
$stmt = null;
} catch (PDOException $e) {
error_log($e->getMessage());
}
我插入IP的部分:
<?php
if( ! filter_var($ip, FILTER_VALIDATE_IP)){
return FALSE;
}
$ip = inet_pton($_SERVER['REMOTE_ADDR']);
try {
$stmt = $db->prepare("INSERT INTO votes(ip, answer) VALUES(?,?)");
$stmt->execute([$ip, $answer]);
$stmt = null;
} catch (PDOException $e) {
return FALSE;
}
最佳答案
首先修复,这很简单:
如果要同时存储 IPv4 和 IPv6 地址,
您应该使用 VARBINARY(16)
而不是 BINARY(16)
。
现在问题来了:为什么 BINARY(16)
不能按预期工作?
假设我们有一个表 ips
,其中只有一列 ip BINARY(16) PRIMARY KEY
。
我们将默认的本地 IPv4 地址存储为
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
并在数据库中找到以下值:
0x7F000001000000000000000000000000
如您所见 - 这是一个 4 字节二进制值 (0x7F000001
)
右填充零以适应 16 字节的固定长度列。
当你现在尝试用
找到它时$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
会发生以下情况:
PHP 发送值 0x7F000001
作为参数,然后进行比较
存储值 0x7F000001000000000000000000000000
。
但是由于两个不同长度的二进制值永远不相等,
WHERE 条件将始终返回 FALSE。
你可以试试
SELECT 0x00 = 0x0000
这将返回 0
(FALSE)。
注意:固定长度的非二进制字符串 (CHAR(N)
) 的行为是不同的。
我们可以使用显式转换作为解决方法:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
它会找到该行。但是如果我们看看我们得到了什么
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
我们会看到
string(8) "7f00:1::"
但这不是(真的)我们试图存储的内容。
当我们现在尝试存储 7f00:1::
时,
我们会得到一个重复键错误,
虽然我们还没有存储任何 IPv6 地址。
再一次:使用 VARBINARY(16)
,您可以保持您的代码不受影响。
如果您存储许多 IPv4 地址,您甚至可以节省一些存储空间。
关于php - 无法选择 ip=inet_pton($ip),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55347251/