web-dev-qa-db-fra.com

Identifiant unique court en php

Je veux créer un identifiant unique mais uniqid() donne quelque chose comme '492607b0ee414'. Ce que je voudrais, c'est quelque chose de similaire à ce que tinyurl donne: '64k8ra'. Plus c'est court, mieux c'est. Les seules exigences sont qu'il ne devrait pas avoir un ordre évident et qu'il devrait être plus joli qu'une séquence de nombres apparemment aléatoire. Les lettres sont préférées aux chiffres et, idéalement, ce ne serait pas un casse mixte. Comme le nombre d'entrées ne sera pas si grand (jusqu'à 10000 environ), le risque de collision n'est pas un facteur énorme.

Toutes suggestions appréciées.

46
Antti

Créez une petite fonction qui renvoie des lettres aléatoires pour une longueur donnée:

<?php
function generate_random_letters($length) {
    $random = '';
    for ($i = 0; $i < $length; $i++) {
        $random .= chr(Rand(ord('a'), ord('z')));
    }
    return $random;
}

Ensuite, vous voudrez appeler cela jusqu'à ce qu'il soit unique, en pseudo-code selon l'endroit où vous stockeriez ces informations:

do {
    $unique = generate_random_letters(6);
} while (is_in_table($unique));
add_to_table($unique);

Vous pouvez également vous assurer que les lettres ne forment pas un mot dans un dictionnaire. Que ce soit l'ensemble du dictionnaire anglais ou simplement un dictionnaire de mauvais mots pour éviter les choses qu'un client trouverait de mauvais goût.

EDIT: J'ajouterais également que cela n'a de sens que si, comme vous avez l'intention de l'utiliser, ce n'est pas pour une grande quantité d'articles, car cela pourrait devenir assez lent avec plus de collisions (obtenir un ID déjà dans le tableau). Bien sûr, vous voudrez une table indexée et vous voudrez ajuster le nombre de lettres dans l'ID pour éviter la collision. Dans ce cas, avec 6 lettres, vous auriez 26 ^ 6 = 308915776 ID uniques possibles (moins les mauvais mots) qui devraient être suffisants pour votre besoin de 10000.

EDIT: Si vous voulez une combinaison de lettres et de chiffres, vous pouvez utiliser le code suivant:

$random .= Rand(0, 1) ? Rand(0, 9) : chr(Rand(ord('a'), ord('z')));
42
lpfavreau

@gen_uuid () par gord.

preg_replace a eu quelques vilains problèmes avec utf-8, ce qui fait que l'uid contient parfois "+" ou "/". Pour contourner cela, vous devez explicitement créer le modèle utf-8

function gen_uuid($len=8) {

    $hex = md5("yourSaltHere" . uniqid("", true));

    $pack = pack('H*', $hex);
    $tmp =  base64_encode($pack);

    $uid = preg_replace("#(*UTF8)[^A-Za-z0-9]#", "", $tmp);

    $len = max(4, min(128, $len));

    while (strlen($uid) < $len)
        $uid .= gen_uuid(22);

    return substr($uid, 0, $len);
}

Ça m'a pris un bon moment pour trouver ça, ça sauve peut-être quelqu'un d'autre un mal de tête

28
Corelgott

Vous pouvez y parvenir avec moins de code:

function gen_uid($l=10){
    return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyz"), 0, $l);
}

Résultat (exemples):

  • cjnp56brdy
  • 9d5uv84zfa
  • ih162lryez
  • ri4ocf6tkj
  • xj04s83egi
24
Nico Schefer

Il existe deux façons d'obtenir un ID fiable unique: rendez-le si long et variable que les risques de collision sont spectaculairement faibles (comme avec un GUID) ou stockez tous les ID générés dans une table pour la recherche (soit en mémoire soit dans une base de données) ou un fichier) pour vérifier l'unicité lors de la génération.

Si vous demandez vraiment comment générer une clé aussi courte et garantir son unicité sans une sorte de vérification en double, la réponse est non.

17
Chris

Voici la routine que j'utilise pour des base62 aléatoires de n'importe quelle longueur ...

L'appel de gen_uuid() renvoie des chaînes comme WJX0u0jV, E9EMaZ3P etc.

Par défaut, cela renvoie 8 chiffres, donc un espace de 64 ^ 8 ou environ 10 ^ 14, ce qui est souvent suffisant pour rendre les collisions assez rares.

Pour une chaîne plus grande ou plus petite, passez $ len comme vous le souhaitez. Aucune limite de longueur, car j'ajoute jusqu'à ce qu'il soit satisfait [jusqu'à la limite de sécurité de 128 caractères, qui peut être supprimée].

Remarque, utilisez un sel aléatoire à l'intérieur du md5 [ou sha1 si vous préférez], afin qu'il ne puisse pas être facilement inversé.

Je n'ai trouvé aucune conversion fiable en base62 sur le Web, d'où cette approche de suppression des caractères du résultat en base64.

Utilisez librement sous licence BSD, profitez,

gord

function gen_uuid($len=8)
{
    $hex = md5("your_random_salt_here_31415" . uniqid("", true));

    $pack = pack('H*', $hex);

    $uid = base64_encode($pack);        // max 22 chars

    $uid = ereg_replace("[^A-Za-z0-9]", "", $uid);    // mixed case
    //$uid = ereg_replace("[^A-Z0-9]", "", strtoupper($uid));    // uppercase only

    if ($len<4)
        $len=4;
    if ($len>128)
        $len=128;                       // prevent silliness, can remove

    while (strlen($uid)<$len)
        $uid = $uid . gen_uuid(22);     // append until length achieved

    return substr($uid, 0, $len);
}
11
gord

Solution vraiment simple:

Faites l'ID unique avec:

$id = 100;
base_convert($id, 10, 36);

Récupérez la valeur d'origine:

intval($str,36);

Je ne peux pas m'en attribuer le mérite car cela provient d'une autre page de débordement de pile, mais je pensais que la solution était si élégante et impressionnante qu'il valait la peine de la copier sur ce fil pour les personnes qui s'y réfèrent.

11
Adcuz

Je suis venu avec ce que je pense être une solution assez cool pour le faire sans vérification d'unicité. Je pensais que je partagerais pour tous les futurs visiteurs.

Un compteur est un moyen très simple de garantir l'unicité ou si vous utilisez une base de données, une clé primaire garantit également l'unicité. Le problème est qu'il semble mauvais et peut être vulnérable. J'ai donc pris la séquence et l'ai mélangée avec un chiffre. Étant donné que le chiffrement peut être inversé, je sais que chaque identifiant est unique tout en restant aléatoire.

C'est python pas php, mais j'ai téléchargé le code ici: https://github.com/adecker89/Tiny-Unique-Identifiers

4
AJD

Vous pouvez utiliser l'ID et le convertir simplement en nombre en base 36 si vous souhaitez le convertir dans les deux sens. Peut être utilisé pour n'importe quelle table avec un identifiant entier.

function toUId($baseId, $multiplier = 1) {
    return base_convert($baseId * $multiplier, 10, 36);
}
function fromUId($uid, $multiplier = 1) {
    return (int) base_convert($uid, 36, 10) / $multiplier;
}

echo toUId(10000, 11111);
1u5h0w
echo fromUId('1u5h0w', 11111);
10000

Les gens intelligents peuvent probablement le découvrir avec suffisamment d'exemples d'identification. Ne laissez pas cette obscurité remplacer la sécurité.

4
OIS

Les lettres sont jolies, les chiffres sont moches. Vous voulez des chaînes aléatoires, mais vous ne voulez pas de chaînes aléatoires "laides"?

Créez un nombre aléatoire et imprimez-le en style alpha (base-26), comme les "numéros" de réservation que les compagnies aériennes donnent.

Pour autant que je sache, il n'y a pas de fonctions de conversion de base à usage général intégrées à PHP, vous devez donc coder ce bit vous-même.

Une autre alternative: utilisez uniqid() et supprimez les chiffres.

function strip_digits_from_string($string) {
    return preg_replace('/[0-9]/', '', $string);
}

Ou remplacez-les par des lettres:

function replace_digits_with_letters($string) {
    return strtr($string, '0123456789', 'abcdefghij');
}
3
RJHunter

Vous pouvez le faire sans des choses impures/coûteuses comme des boucles, des concaténations de chaînes ou plusieurs appels à Rand (), d'une manière claire et facile à lire. Aussi, il vaut mieux utiliser mt_Rand():

function createRandomString($length)
{
    $random = mt_Rand(0, (1 << ($length << 2)) - 1);
    return dechex($random);
}

Si vous avez besoin que la chaîne ait la longueur exacte dans tous les cas, remplissez simplement le nombre hexadécimal avec des zéros:

function createRandomString($length)
{
    $random = mt_Rand(0, (1 << ($length << 2)) - 1);
    $number = dechex($random);
    return str_pad($number, $length, '0', STR_PAD_LEFT);
}

Le "backdraw théorique" est que vous êtes limité aux capacités des PHP - mais c'est plus une question philosophique dans ce cas;) Passons-y quand même:

  • PHP est limité dans ce qu'il peut représenter comme un nombre hexadécimal en le faisant comme ceci. Ce serait $length <= 8 au moins sur un système 32 bits, où la limitation des PHP devrait être de 4.294.967.295.
  • Le générateur de nombres aléatoires PHP a également un maximum. Pour mt_Rand() au moins sur un système 32 bits, il devrait être 2.147.483.647
  • Vous êtes donc théoriquement limité à 2.147.483.647 ID.

Pour en revenir au sujet, la do { (generate ID) } while { (id is not uniqe) } (insert id) intuitive a un inconvénient et une faille possible qui pourraient vous conduire directement dans l'obscurité ...

Inconvénient: La validation est pessimiste. Le faire comme ça toujours nécessite une vérification dans la base de données. Avoir suffisamment d'espace de clés (par exemple une longueur de 5 pour vos entrées de 10 000) entraînera très probablement des collisions aussi souvent, car cela pourrait être comparable moins de ressources consommant juste essayez de stocker les données et réessayez uniquement en cas d'erreur UNIQUE KEY.

Faille: L'utilisateur A récupère un ID qui est vérifié comme non encore utilisé. Ensuite, le code essaiera d'insérer les données. Mais en attendant, l'utilisateur B est entré dans la même boucle et récupère malheureusement le même nombre aléatoire, car l'utilisateur A n'est pas encore stocké et cet ID était toujours libre. Maintenant, le système stocke soit l'utilisateur B ou l'utilisateur A , et lors de la tentative pour stocker le deuxième utilisateur, il y a déjà l'autre en attendant - ayant le même ID.

Vous devrez dans tous les cas gérer cette exception et réessayer l'insertion avec un ID nouvellement créé. L'ajout de cela tout en conservant la boucle de vérification pessimiste (que vous devrez ressaisir) se traduira par un code assez moche et difficile à suivre. Heureusement, la solution à cela est la même que celle à l'inconvénient: Allez-y en premier lieu et essayez de stocker les données. En cas d'erreur UNIQUE KEY, réessayez simplement avec un nouvel ID.

1
nico gawenda

Jetez un œil à cet article

Il explique comment générer des identifiants uniques courts à partir de vos identifiants bdd, comme le fait YouTube.

En fait, la fonction dans l'article est très liée à fonction php base_convert qui convertit un nombre d'une base en une autre (mais n'est que jusqu'à la base 36).

1
httpete

Vous pouvez également le faire comme ceci:

public static function generateCode($length = 6)
    {
        $az = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $azr = Rand(0, 51);
        $azs = substr($az, $azr, 10);
        $stamp = hash('sha256', time());
        $mt = hash('sha256', mt_Rand(5, 20));
        $alpha = hash('sha256', $azs);
        $hash = str_shuffle($stamp . $mt . $alpha);
        $code = ucfirst(substr($hash, $azr, $length));
        return $code;
    }
1
Aldee

Si vous aimez une version plus longue de l'identifiant unique, utilisez ceci:
$ uniqueid = sha1 (md5 (time ()));

0
Alon Kogan

Meilleure réponse: La plus petite chaîne unique "Hash Like" avec un ID de base de données unique - PHP, aucune bibliothèque tierce n'est requise.

Voici le code:

<?php
/*
THE FOLLOWING CODE WILL PRINT:
A database_id value of 200 maps to 5K
A database_id value of 1 maps to 1
A database_id value of 1987645 maps to 16LOD
*/
$database_id = 200;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 200 maps to $base36value\n";
$database_id = 1;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1 maps to $base36value\n";
$database_id = 1987645;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1987645 maps to $base36value\n";

// HERE'S THE FUNCTION THAT DOES THE HEAVY LIFTING...
function dec2string ($decimal, $base)
// convert a decimal number into a string using $base
{
    //DebugBreak();
   global $error;
   $string = null;

   $base = (int)$base;
   if ($base < 2 | $base > 36 | $base == 10) {
      echo 'BASE must be in the range 2-9 or 11-36';
      exit;
   } // if

   // maximum character string is 36 characters
   $charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

   // strip off excess characters (anything beyond $base)
   $charset = substr($charset, 0, $base);

   if (!ereg('(^[0-9]{1,50}$)', trim($decimal))) {
      $error['dec_input'] = 'Value must be a positive integer with < 50 digits';
      return false;
   } // if

   do {
      // get remainder after dividing by BASE
      $remainder = bcmod($decimal, $base);

      $char      = substr($charset, $remainder, 1);   // get CHAR from array
      $string    = "$char$string";                    // prepend to output

      //$decimal   = ($decimal - $remainder) / $base;
      $decimal   = bcdiv(bcsub($decimal, $remainder), $base);

   } while ($decimal > 0);

   return $string;

}

?>
0
John Erck