Funkcja rand() w PHP służy do generowania pseudolosowych liczb całkowitych. Można użyć jej bez argumentów albo określić minimalną i maksymalną wartość, np. rand(1, 100). Choć składnia jest bardzo prosta, przy praktycznym użyciu pojawia się kilka ważnych pytań: czy wartości graniczne mogą zostać wylosowane, jak uzyskać liczbę ujemną, jak zasymulować określone prawdopodobieństwo, czym rand() różni się od mt_rand() i dlaczego nie należy używać go do generowania haseł, tokenów czy kodów bezpieczeństwa.
Współczesne PHP oferuje już bardziej rozbudowane API do losowości, w tym random_int() oraz klasę Random\Randomizer. Mimo to rand() nadal występuje w ogromnej liczbie skryptów i pozostaje dobrym punktem wyjścia do zrozumienia sposobu generowania liczb pseudolosowych w PHP. Poniżej przejdziemy od podstawowej składni do bardziej praktycznych przypadków użycia.
rand() w PHP - jak działa?
Najprostsze wywołanie funkcji wygląda tak:
$number = rand();
echo $number;
Bez podania argumentów rand() zwraca pseudolosową liczbę całkowitą z zakresu od 0 do wartości zwracanej przez getrandmax().
Możemy sprawdzić maksymalną wartość:
echo getrandmax();
W praktycznych aplikacjach znacznie częściej korzysta się jednak z wersji przyjmującej dwa argumenty:
rand($min, $max);
$min oznacza najmniejszą dopuszczalną wartość, natomiast $max - największą.
Losowanie liczby z określonego zakresu
Jeżeli potrzebujemy liczby od 1 do 10, wystarczy:
$number = rand(1, 10);
echo $number;
Możliwym wynikiem jest:
7
Przy kolejnym uruchomieniu skryptu może pojawić się jednak np. 2, 9 albo 10.
Zakres może być znacznie większy:
$id = rand(100000, 999999);
echo $id;
W ten sposób otrzymamy sześciocyfrową liczbę. Nie oznacza to jednak, że taki kod nadaje się automatycznie do zastosowań związanych z uwierzytelnianiem - do tego wrócimy w dalszej części artykułu.
Czy wartości min i max mogą zostać wylosowane?
Tak. To ważny szczegół funkcji rand(). Zakres jest domknięty z obu stron.
Dla:
rand(1, 3);
możliwe wyniki to dokładnie:
1
2
3
Zarówno 1, jak i 3 mogą zostać zwrócone. Nie należy więc dodawać sztucznie jedynki do górnej granicy.
Błędem byłoby np. zapisanie:
rand(1, 11);
jeżeli oczekiwany zakres miał wynosić od 1 do 10.
Losowanie liczb ujemnych
rand() nie ogranicza nas do wartości dodatnich. Możemy wskazać ujemne minimum:
$temperature = rand(-20, 20);
echo $temperature;
Wynikiem może być np.:
-7
albo:
14
Możliwa jest również wartość 0, ponieważ znajduje się we wskazanym przedziale.
Takie losowanie może być przydatne np. podczas generowania danych testowych, symulacji zmian temperatury, losowych przesunięć współrzędnych czy testowania algorytmów pracujących z wartościami po obu stronach zera.
Przykład - rzut kostką w PHP
Klasyczny przykład zastosowania rand() to symulacja rzutu sześcienną kostką:
$dice = rand(1, 6);
echo "Wyrzucono: " . $dice;
Każda z sześciu wartości należy do zakresu funkcji.
Możemy również zasymulować rzut dwiema kostkami:
$dice1 = rand(1, 6);
$dice2 = rand(1, 6);
$total = $dice1 + $dice2;
echo "Pierwsza kostka: " . $dice1 . PHP_EOL;
echo "Druga kostka: " . $dice2 . PHP_EOL;
echo "Suma: " . $total;
Tutaj pojawia się ciekawy szczegół statystyczny. Chociaż każda kostka osobno generuje wartości od 1 do 6, suma od 2 do 12 nie ma równomiernego rozkładu. Wynik 7 można uzyskać na więcej sposobów niż 2 lub 12.
Nie jest to problem funkcji rand(), lecz właściwość samego eksperymentu.
Losowanie z określonym prawdopodobieństwem
Częstym zadaniem jest wykonanie czegoś z określonym prawdopodobieństwem. Załóżmy, że zdarzenie powinno wystąpić w około 20% przypadków.
$draw = rand(1, 100);
if ($draw <= 20) {
echo "Zdarzenie wystąpiło";
}
Wartości od 1 do 20 stanowią 20 spośród 100 możliwych wyników.
Możemy zamknąć ten mechanizm w funkcji:
function chance(int $percent): bool
{
return rand(1, 100) <= $percent;
}
if (chance(25)) {
echo "Trafienie";
}
Taka implementacja jest czytelna dla prostych symulacji, gier czy danych demonstracyjnych.
W kodzie produkcyjnym warto jednak dodatkowo zweryfikować zakres argumentu $percent, aby nie dopuścić np. wartości -10 albo 150.
function chance(int $percent): bool
{
if ($percent < 0 || $percent > 100) {
throw new InvalidArgumentException(
"Procent musi mieścić się w zakresie 0-100"
);
}
```
return rand(1, 100) <= $percent;
```
}
Losowanie true lub false
Najprostszy sposób na losowanie wartości logicznej to zakres 0-1:
$result = (bool) rand(0, 1);
var_dump($result);
Możemy też zapisać warunek bardziej jawnie:
$result = rand(0, 1) === 1;
var_dump($result);
Przy dwóch możliwych wartościach otrzymujemy model zdarzenia o prawdopodobieństwie około 50%.
Może to posłużyć do symulacji rzutu monetą:
$coin = rand(0, 1);
echo $coin === 1 ? "orzeł" : "reszka";
Losowanie elementu tablicy
Teoretycznie do wylosowania elementu zwykłej indeksowanej tablicy można wykorzystać rand():
$colors = [
"czerwony",
"zielony",
"niebieski",
"żółty"
];
$index = rand(0, count($colors) - 1);
echo $colors[$index];
Takie rozwiązanie działa, ponieważ indeksy tablicy zaczynają się od 0 i są kolejne.
Nie jest jednak uniwersalne. Tablica może mieć np. klucze tekstowe:
$roles = [
"admin" => "Administrator",
"editor" => "Redaktor",
"user" => "Użytkownik"
];
W takim przypadku lepiej wykorzystać funkcję stworzoną właśnie do losowania kluczy:
$key = array_rand($roles);
echo $roles[$key];
array_rand() zwraca klucz losowego elementu, a nie samą wartość. Przy drugim argumencie większym od 1 zwraca tablicę kluczy.
$numbers = [10, 20, 30, 40, 50];
$keys = array_rand($numbers, 2);
echo $numbers[$keys[0]] . PHP_EOL;
echo $numbers[$keys[1]];
rand() a mt_rand() - jaka jest różnica?
W starszych materiałach PHP można spotkać zalecenie, aby zamiast rand() używać mt_rand(). Historycznie były to dwa różne rozwiązania.
mt_rand() korzysta z generatora Mersenne Twister i przez długi czas był szybszym oraz lepiej zdefiniowanym odpowiednikiem starszego rand().
Od PHP 7.1 sytuacja wygląda inaczej. rand() korzysta z tego samego generatora co mt_rand().
$a = rand(1, 100);
$b = mt_rand(1, 100);
Nie oznacza to jednak, że w nowym kodzie należy wybierać między nimi wyłącznie na podstawie długości nazwy. Współczesne PHP posiada rozszerzenie Random i dokumentacja zaleca dla nowo pisanego kodu korzystanie z Random\Randomizer.
| Mechanizm | Charakterystyka | Zastosowanie |
|---|---|---|
rand() |
globalny Mt19937 | starszy i prosty kod |
mt_rand() |
Mersenne Twister | starszy kod wymagający tego API |
random_int() |
bezpieczne źródło systemowe | wartości wymagające nieprzewidywalności |
Random\Randomizer |
API oparte na wymiennych silnikach | nowy kod i bardziej świadome zarządzanie losowością |
srand(), seed i powtarzalne wyniki
Generator pseudolosowy nie tworzy wartości w sposób magicznie przypadkowy. Generuje sekwencję na podstawie swojego stanu.
W starszym kodzie można spotkać ręczne ustawianie ziarna:
srand(12345);
echo rand(1, 100) . PHP_EOL;
echo rand(1, 100) . PHP_EOL;
echo rand(1, 100);
Ponowne ustawienie tego samego ziarna pozwala uzyskać powtarzalną sekwencję generatora. Taka właściwość może być przydatna podczas testowania albo odtwarzania symulacji.
Nie ma natomiast potrzeby wykonywania czegoś takiego przy zwykłym korzystaniu z rand():
srand(time());
PHP sam inicjalizuje generator. Ręczne seedowanie warto stosować tylko wtedy, gdy istnieje konkretny powód, np. potrzeba odtworzenia tej samej sekwencji w teście.
Trzeba też pamiętać, że rand(), mt_rand(), srand() i mt_srand() korzystają obecnie ze wspólnego globalnego stanu generatora Mt19937. Wywołanie jednej funkcji wpływa więc na dalszą sekwencję generowaną przez pozostałe.
Dlaczego rand() nie nadaje się do tokenów i haseł?
Najważniejszym ograniczeniem rand() jest to, że funkcja nie zapewnia kryptograficznie bezpiecznej losowości.
Nie należy używać jej do generowania:
- tokenów resetowania hasła,
- tokenów aktywacyjnych,
- kluczy API,
- sekretów sesyjnych,
- kluczy kryptograficznych,
- wartości, których odgadnięcie mogłoby zagrozić bezpieczeństwu aplikacji.
Taki kod jest więc złym pomysłem:
$token = rand(10000000, 99999999);
Problemem nie jest tylko liczba możliwych kombinacji. Generator Mersenne Twister jest deterministycznym generatorem pseudolosowym i nie został zaprojektowany tak, aby jego wyniki były nieprzewidywalne dla atakującego.
rand() czy random_int()?
Jeżeli wartość ma znaczenie dla bezpieczeństwa, PHP udostępnia random_int().
$code = random_int(100000, 999999);
echo $code;
Podobnie jak rand(), funkcja przyjmuje minimum i maksimum, a przedział obejmuje obie granice. Różnica dotyczy źródła losowości.
random_int() korzysta ze źródła kryptograficznie bezpiecznej losowości udostępnianego przez system operacyjny. Dzięki temu nadaje się do sytuacji, w których wynik powinien być trudny do przewidzenia.
Oczywiście sam bezpiecznie wygenerowany sześciocyfrowy kod nie zabezpiecza jeszcze systemu logowania. Aplikacja powinna również ograniczać liczbę prób, stosować czas ważności kodu i chronić cały proces uwierzytelniania.
Do tokenów tekstowych częściej wykorzystuje się natomiast random_bytes(), np.:
$token = bin2hex(random_bytes(32));
echo $token;
32 losowe bajty po konwersji przez bin2hex() dają 64 znaki zapisu szesnastkowego.
Random\Randomizer - współczesne API PHP
Od PHP 8.2 dostępna jest klasa Random\Randomizer. Stanowi ona wysokopoziomowe API dla generatorów losowości i jest obecnie rozwiązaniem preferowanym w nowo tworzonym kodzie.
Najprostszy przykład:
$randomizer = new Random\Randomizer();
$number = $randomizer->getInt(1, 100);
echo $number;
Jeżeli konstruktor otrzyma domyślne null, PHP użyje silnika Random\Engine\Secure.
Randomizer daje więcej możliwości niż samo losowanie liczby. Może m.in. wybierać klucze z tablicy albo ją tasować.
$randomizer = new Random\Randomizer();
$users = [
"Anna",
"Marek",
"Julia",
"Tomasz"
];
$shuffled = $randomizer->shuffleArray($users);
print_r($shuffled);
Możemy też jawnie przekazać inny silnik, np. Mersenne Twister:
$engine = new Random\Engine\Mt19937();
$randomizer = new Random\Randomizer($engine);
echo $randomizer->getInt(1, 100);
To rozdzielenie API od generatora jest ważną różnicą względem globalnej funkcji rand(). Kod może świadomie korzystać z generatora odpowiedniego do konkretnego zastosowania.
Najczęstsze błędy przy używaniu rand()
Sama funkcja jest prosta, ale wokół niej powtarza się kilka błędów.
Dodawanie 1 do górnego zakresu
Jeżeli chcemy liczby 1-10, poprawny zapis to:
rand(1, 10);
a nie:
rand(1, 11);
Wartość maksymalna jest częścią zakresu.
Losowanie indeksu bez uwzględnienia długości tablicy
Dla tablicy mającej 10 elementów indeksy zwykle mieszczą się w zakresie 0-9, nie 1-10.
$index = rand(0, count($items) - 1);
Jeszcze bezpieczniej przy dowolnych kluczach użyć array_rand().
Używanie rand() do bezpieczeństwa
To najpoważniejszy błąd. Do wartości, które muszą pozostać nieprzewidywalne, należy używać random_int(), random_bytes() albo odpowiedniego silnika w Random\Randomizer.
Ręczne seedowanie bez potrzeby
Kod typu:
srand(time());
nie jest potrzebny w typowym skrypcie. PHP automatycznie inicjalizuje generator.
Traktowanie pseudolosowości jak prawdziwej losowości
rand() wykorzystuje algorytmiczny generator pseudolosowy. Dla symulacji, przykładowych danych, gier czy losowego wyboru elementu może być wystarczający, ale jego właściwości są inne niż źródła kryptograficznej losowości.
Czy warto jeszcze używać rand() w PHP?
rand() nadal działa w PHP 8 i jest prostą funkcją do losowania liczb całkowitych. Dlatego znajomość jej składni ma sens, szczególnie przy analizowaniu starszego kodu oraz przy prostych przykładach.
Dla prostego skryptu:
$dice = rand(1, 6);
nie ma potrzeby budowania skomplikowanej architektury tylko po to, aby zasymulować rzut kostką.
W nowym kodzie warto jednak znać podział zastosowań:
rand()- prosty, starszy interfejs do pseudolosowych liczb,random_int()- prosty interfejs do bezpiecznych, nieprzewidywalnych liczb całkowitych,random_bytes()- bezpieczne losowe bajty, np. do tokenów,Random\Randomizer- nowoczesne API z możliwością świadomego wyboru silnika.
Najważniejsze jest więc nie samo pytanie "jak wylosować liczbę w PHP?", lecz "jakiego rodzaju losowości potrzebuje aplikacja?". Do rzutu kostką i danych demonstracyjnych wystarczy generator pseudolosowy. Do tokenu resetowania hasła potrzebna jest natomiast losowość kryptograficznie bezpieczna. Te dwa przypadki mogą wyglądać podobnie w kodzie, ale mają zupełnie inne wymagania.

Komentarze