@@ -559,7 +559,7 @@ function random_string(string $type = 'alnum', int $len = 8): string
559559 break ;
560560 }
561561
562- return substr ( str_shuffle ( str_repeat ( $ pool , ( int ) ceil ( $ len / strlen ( $ pool )))), 0 , $ len );
562+ return _from_random ( $ len, $ pool );
563563
564564 case 'numeric ' :
565565 $ max = 10 ** $ len - 1 ;
@@ -587,6 +587,69 @@ function random_string(string $type = 'alnum', int $len = 8): string
587587 }
588588}
589589
590+ if (! function_exists ('_from_random ' )) {
591+ /**
592+ * The following function was derived from code of Symfony (v6.2.7 - 2023-02-28)
593+ * https://github.com/symfony/symfony/blob/80cac46a31d4561804c17d101591a4f59e6db3a2/src/Symfony/Component/String/ByteString.php#L45
594+ * Code subject to the MIT license (https://github.com/symfony/symfony/blob/v6.2.7/LICENSE).
595+ * Copyright (c) 2004-present Fabien Potencier
596+ *
597+ * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
598+ * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
599+ * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
600+ * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
601+ *
602+ * @internal Outside the framework this should not be used directly.
603+ */
604+ function _from_random (int $ length , string $ pool ): string
605+ {
606+ if ($ length <= 0 ) {
607+ throw new InvalidArgumentException (
608+ sprintf ('A strictly positive length is expected, "%d" given. ' , $ length )
609+ );
610+ }
611+
612+ $ poolSize = \strlen ($ pool );
613+ $ bits = (int ) ceil (log ($ poolSize , 2.0 ));
614+ if ($ bits <= 0 || $ bits > 56 ) {
615+ throw new InvalidArgumentException (
616+ 'The length of the alphabet must in the [2^1, 2^56] range. '
617+ );
618+ }
619+
620+ $ string = '' ;
621+
622+ while ($ length > 0 ) {
623+ $ urandomLength = (int ) ceil (2 * $ length * $ bits / 8.0 );
624+ $ data = random_bytes ($ urandomLength );
625+ $ unpackedData = 0 ;
626+ $ unpackedBits = 0 ;
627+
628+ for ($ i = 0 ; $ i < $ urandomLength && $ length > 0 ; $ i ++) {
629+ // Unpack 8 bits
630+ $ unpackedData = ($ unpackedData << 8 ) | \ord ($ data [$ i ]);
631+ $ unpackedBits += 8 ;
632+
633+ // While we have enough bits to select a character from the alphabet, keep
634+ // consuming the random data
635+ for (; $ unpackedBits >= $ bits && $ length > 0 ; $ unpackedBits -= $ bits ) {
636+ $ index = ($ unpackedData & ((1 << $ bits ) - 1 ));
637+ $ unpackedData >>= $ bits ;
638+ // Unfortunately, the alphabet size is not necessarily a power of two.
639+ // Worst case, it is 2^k + 1, which means we need (k+1) bits and we
640+ // have around a 50% chance of missing as k gets larger
641+ if ($ index < $ poolSize ) {
642+ $ string .= $ pool [$ index ];
643+ $ length --;
644+ }
645+ }
646+ }
647+ }
648+
649+ return $ string ;
650+ }
651+ }
652+
590653if (! function_exists ('increment_string ' )) {
591654 /**
592655 * Add's _1 to a string or increment the ending number to allow _2, _3, etc
0 commit comments