Невозможно выбрать, где ip = inet_pton ($ ip) - PullRequest
14 голосов
/ 26 марта 2019

У меня есть уникальный столбец в базе данных с именем ip

IP-адреса сохраняются в этом столбце как BINARY(16) (без сопоставления) после преобразования их с помощью функции PHP

$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;
}

Ответы [ 3 ]

12 голосов
/ 30 марта 2019

Первое исправление, которое довольно просто: Если вы хотите сохранить оба, адреса 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 (ЛОЖЬ).

Примечание. Поведение отличается для недвоичных строк фиксированной длины (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.

4 голосов
/ 01 апреля 2019

Вместо того, чтобы бороться за выход BINARY, давайте избегать этого.

INSERT INTO ips (ip) VALUES(INET6_ATON(?))

и

SELECT INET6_NTOA(ip) FROM ips WHERE ...;

Таким образом, вы работаете только с читаемыми человеком строками.

Примечания:

  • Пропустите использование inet_pton() в PHP, поскольку преобразование в настоящее время выполняется в MySQL.
  • Функции INET6... не существуют встарые версии MySQL.
  • Да, используйте VARBINARY(16) и обязательно проверьте, будут ли работать строки IPv4 (например, «1.2.3.4»).
3 голосов
/ 01 апреля 2019

Я не буду отвечать , почему ваш код не работал должным образом, потому что я точно не знаю.Спасибо за отличный ответ @Paul Spiegel, он объяснил почему.

В этом ответе я просто предлагаю вам использовать встроенные функции MySQL вместо PHP.

Этокак я обрабатываю IP-адреса в своих приложениях, и до сих пор у меня нет проблем с этой моделью.

Я храню IP-адреса в столбце varbinary(16) и выполняю преобразования, используя встроенные функции MySQL

  1. inet6_aton для преобразования IP-строк в двоичные

  2. inet6_ntoa для преобразования двоичных в IP-строки

Поэтому замените этот код

//query 1 
$ip = inet_pton($_SERVER['REMOTE_ADDR']);
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=?");
$stmt->execute([$ip]);

на этот

//query 2 
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=INET6_ATON(?)");
$stmt->execute([$_SERVER['REMOTE_ADDR']]);

Нет необходимости говорить, что НЕ делайте так (запрос 3) -

//query 3 
$stmt = $db->prepare("SELECT * FROM `votes` WHERE INET6_NTOA(ip)= ?");
$stmt->execute([$_SERVER['REMOTE_ADDR']]);

(поскольку База данных будет ненавидеть вас за то, что она будет выполнять преобразование для каждой IP-записи в таблице)

Из своего короткого опыта я обнаружила, что всякий раз, когда у меня естьвозможность позволить базе данных делать что-то вместо прикладного уровня (PHP), пусть база данных сделает это немедленноiately.Сделайте MySQL толстым, а PHP - тощим =), как говорят жирная модель и тощий контроллер.

Когда вы выполняете большую часть своей работы внутри базы данных, это сделает вашу базу данных лучше работающей независимо от кода PHP (этоне требуется), что делает вашу базу данных более переносимой.

Например, если вы хотите превратить свою систему из облачной веб-системы, использующей PHP, в локальную систему, котораяиспользует .net языки, разработчики .net будут любить вас за то, что вы сделаете их меньше, потому что большая часть работы уже написана и выполнена MySQL.

Другой пример, ваше приложение успешно работает, и теперь у вас большеклиенты, которым нужно ваше приложение, вы просто получите другой сервер и установите MySQL только на нем, а поскольку большая часть работы выполняется базой данных, ваше приложение масштабируется проще, чем установка полного веб-сервера, и выполняйте масштабирование также для PHP,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...