PHP предоставляет популярную хеш-функцию md5 () , которая возвращает 32 шестнадцатеричную строку символов. Это отличный способ создать отпечаток для любой строки произвольной длины. Но что, если вам нужно сгенерировать целое число из URL?
Вызов
Мы столкнулись с этой проблемой в RatingWidget, когда нам пришлось привязать наши рейтинговые виджеты к уникальным идентификаторам Int64 на основе страницы сайта, с которой они загружаются. Теоретически мы могли бы просто хранить URL-адреса и запрашивать столбец URL, но URL-адреса могут быть очень длинными, и создание индекса для текстового столбца с неизвестной длиной очень неэффективно.
Так что если вы работаете над разработкой динамического виджета любого вида, который должен загружать разные данные в зависимости от URL, с которого он загружен, этот пост сэкономит вам массу времени.
Чтобы упростить проблему, давайте разделим ее на две подзадачи:
- Канонизация URL
- Строка в уникальное преобразование Int64
Канонизация URL
В нашем случае мы хотели назначить уникальный Int64 для страницы, а не для URL. Например, http://domain.com?x=1&y=2
http://domain.com?y=2&x=1
Поэтому мы хотели присвоить им идентичный Int64 ID. Таким образом, канонизируя URL-адреса перед их отображением в Int64, мы можем преобразовать URL-адреса в единообразное представление.
function canonizeUrl($url)
{
$url = parse_url(strtolower($url));
$canonic = $url['host'] . $url['path'];
if (isset($url['query']))
{
parse_str($url['query'], $queryString);
$canonic .= '?' . canonizeQueryString($queryString);
}
return $canonic;
}
function canonizeQueryString(array $params)
{
if (!is_array($params) || 0 === count($params))
return '';
// Urlencode both keys and values.
$keys = urlencode_rfc3986(array_keys($params));
$values = urlencode_rfc3986(array_values($params));
$params = array_combine($keys, $values);
// Parameters are sorted by name, using lexicographical byte value ordering.
// Ref: Spec: 9.1.1 (1)
uksort($params, 'strcmp');
$pairs = array();
foreach ($params as $parameter => $value)
{
$lower_param = strtolower($parameter);
if (is_array($value))
{
// If two or more parameters share the same name, they are sorted by their value
// Ref: Spec: 9.1.1 (1)
natsort($value);
foreach ($value as $duplicate_value)
$pairs[] = $lower_param . '=' . $duplicate_value;
}
else
{
$pairs[] = $lower_param . '=' . $value;
}
}
if (0 === count($pairs))
return '';
return implode('&', $pairs);
}
function urlencode_rfc3986($input)
{
if (is_array($input))
return array_map(array(&$this, 'urlencode_rfc3986'), $input);
else if (is_scalar($input))
return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($input)));
return '';
}
По сути, этот код переупорядочивает параметры строки запроса по лексикографическому порядку и слегка подправляет кодировку URL на основе стандарта синтаксиса URI RFC 3986 , чтобы компенсировать несоответствие кодировки URL различных браузеров + сервера.
Примечания:
- В нашем случае canonizeUrl, функция канонизации, избавляется от протокола. Так что
https://domain.com
http://domain.com
domain.com
- Как вы можете заметить, мы также игнорируем все после фрагмента хешмарка. Поэтому, если вы хотите создать уникальные идентификаторы для SPA ( одностраничного приложения ) различных состояний, таких как
http://my-spa.com/#state1
http://my-spa.com/#state2
Преобразование строки в уникальный идентификатор Int64 для индексированного столбца MySql BIGINT
После дурачения с различными функциями преобразования битов, такими как bindec()
decbin()
base_convert()
Мы обнаружили, что 64-битные целые числа и PHP не очень хорошо играют. Ни одна из упомянутых функций последовательно не поддерживает 64-битные Покопавшись в Google, мы получили сообщение о 32-битных ограничениях в PHP, которое включало в себя предложение использовать GMP , действительно классную библиотеку для целых чисел с множественной точностью. Используя эту библиотеку, нам удалось создать эту однострочную хеш-функцию, которая генерирует 64-битное целое число из строки произвольной длины.
function get64BitHash($str)
{
return gmp_strval(gmp_init(substr(md5($str), 0, 16), 16), 10);
}
Постфактум, мы могли бы реализовать алгоритм CRC64, который генерирует контрольную сумму строки и должен работать быстрее, чем MD5. Но преимущество метода, который мы использовали по сравнению с CRC, заключается в том, что мы создали однонаправленную хэш-функцию, поэтому мы можем повторно использовать ее для различных целей криптографии в коде.
Чтобы узнать больше о GMP, смотрите здесь .
Супер финал
Сочетая канонизацию URL с отображением String на Int64, окончательное решение выглядит следующим образом:
function urlTo64BitHash($url)
{
return get64BitHash(canonizeUrl($url));
}
Тест столкновения и производительности get64BitHash
Платформа: Intel i3, Windows 7 64 бит, PHP 5.3
Итерации: 10 000 000 раз сгенерировано get64BitHash
Истекшее время: 460 миллисекунд на каждые 100 000 поколений
Столкновение: не найдено
Резюме
Я надеюсь, что это простое решение сэкономит ваше время на вашем следующем проекте. Если у вас есть комментарии или какие-либо дополнительные варианты использования этой техники, пожалуйста, не стесняйтесь комментировать ниже.