Eerst de oplossing, die vrij eenvoudig is:als u zowel IPv4- als IPv6-adressen wilt opslaan, moet u VARBINARY(16)
gebruiken in plaats van BINARY(16)
.
Nu het probleem:waarom werkt het niet zoals verwacht met BINARY(16)
?
Overweeg dat we een tabel hebben ips
met slechts één kolom ip BINARY(16) PRIMARY KEY
.We slaan het standaard lokale IPv4-adres op met
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
en zoek de volgende waarde in de database:
0x7F000001000000000000000000000000
Zoals je ziet - Het is een binaire waarde van 4 bytes (0x7F000001
)rechts opgevuld met nullen om in de kolom met vaste lengte van 16 bytes te passen.
Wanneer je het nu probeert te vinden met
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
het volgende gebeurt:PHP stuurt de waarde 0x7F000001
als parameter die vervolgens wordt vergeleken met de opgeslagen waarde 0x7F000001000000000000000000000000
.Maar aangezien twee binaire waarden van verschillende lengte nooit gelijk zijn, zal de WHERE-voorwaarde altijd FALSE retourneren. U kunt het proberen met
SELECT 0x00 = 0x0000
wat 0
zal opleveren (FALSE).
Opmerking:het gedrag is anders voor niet-binaire tekenreeksen met een vaste lengte (CHAR(N)
).
We zouden expliciet casten als tijdelijke oplossing kunnen gebruiken:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
en het zal de rij vinden. Maar als we kijken naar wat we krijgen
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
we zullen zien
string(8) "7f00:1::"
Maar dat is niet (echt) wat we hebben geprobeerd op te slaan. En als we nu 7f00:1::
proberen op te slaan , krijgen we een fout met dubbele sleutel , hoewel we nog nooit een IPv6-adres hebben opgeslagen.
Dus nogmaals:gebruik VARBINARY(16)
, en u kunt uw code ongewijzigd laten. U bespaart zelfs wat opslagruimte als u veel IPv4-adressen opslaat.