[String] allow passing a string of custom characters to ByteString::fromRandom
This commit is contained in:
parent
4d8a4b6ede
commit
5d15c0be60
@ -25,20 +25,64 @@ use Symfony\Component\String\Exception\RuntimeException;
|
|||||||
*/
|
*/
|
||||||
class ByteString extends AbstractString
|
class ByteString extends AbstractString
|
||||||
{
|
{
|
||||||
|
private const ALPHABET_ALPHANUMERIC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
|
||||||
public function __construct(string $string = '')
|
public function __construct(string $string = '')
|
||||||
{
|
{
|
||||||
$this->string = $string;
|
$this->string = $string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromRandom(int $length = 16): self
|
/*
|
||||||
|
* The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
|
||||||
|
*
|
||||||
|
* https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
|
||||||
|
*
|
||||||
|
* Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static function fromRandom(int $length = 16, string $alphabet = null): self
|
||||||
{
|
{
|
||||||
$string = '';
|
if ($length <= 0) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Expected positive length value, got "%d".', $length));
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
$alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
|
||||||
$string .= str_replace(['/', '+', '='], '', base64_encode(random_bytes($length)));
|
$alphabetSize = \strlen($alphabet);
|
||||||
} while (\strlen($string) < $length);
|
$bits = (int) ceil(log($alphabetSize, 2.0));
|
||||||
|
if ($bits <= 0 || $bits > 56) {
|
||||||
|
throw new InvalidArgumentException('Expected $alphabet\'s length to be in [2^1, 2^56].');
|
||||||
|
}
|
||||||
|
|
||||||
return new static(substr($string, 0, $length));
|
$ret = '';
|
||||||
|
while ($length > 0) {
|
||||||
|
$urandomLength = (int) ceil(2 * $length * $bits / 8.0);
|
||||||
|
$data = random_bytes($urandomLength);
|
||||||
|
$unpackedData = 0;
|
||||||
|
$unpackedBits = 0;
|
||||||
|
for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
|
||||||
|
// Unpack 8 bits
|
||||||
|
$unpackedData = ($unpackedData << 8) | \ord($data[$i]);
|
||||||
|
$unpackedBits += 8;
|
||||||
|
|
||||||
|
// While we have enough bits to select a character from the alphabet, keep
|
||||||
|
// consuming the random data
|
||||||
|
for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
|
||||||
|
$index = ($unpackedData & ((1 << $bits) - 1));
|
||||||
|
$unpackedData >>= $bits;
|
||||||
|
// Unfortunately, the alphabet size is not necessarily a power of two.
|
||||||
|
// Worst case, it is 2^k + 1, which means we need (k+1) bits and we
|
||||||
|
// have around a 50% chance of missing as k gets larger
|
||||||
|
if ($index < $alphabetSize) {
|
||||||
|
$ret .= $alphabet[$index];
|
||||||
|
--$length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static($ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bytesAt(int $offset): array
|
public function bytesAt(int $offset): array
|
||||||
|
@ -12,6 +12,7 @@ CHANGELOG
|
|||||||
depending of the input string UTF-8 compliancy
|
depending of the input string UTF-8 compliancy
|
||||||
* added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()`
|
* added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()`
|
||||||
* added `AbstractString::containsAny()`
|
* added `AbstractString::containsAny()`
|
||||||
|
* allow passing a string of custom characters to `ByteString::fromRandom()`
|
||||||
|
|
||||||
5.0.0
|
5.0.0
|
||||||
-----
|
-----
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\String\Tests;
|
namespace Symfony\Component\String\Tests;
|
||||||
|
|
||||||
use Symfony\Component\String\AbstractString;
|
use Symfony\Component\String\AbstractString;
|
||||||
|
use function Symfony\Component\String\b;
|
||||||
use Symfony\Component\String\ByteString;
|
use Symfony\Component\String\ByteString;
|
||||||
|
|
||||||
class ByteStringTest extends AbstractAsciiTestCase
|
class ByteStringTest extends AbstractAsciiTestCase
|
||||||
@ -21,6 +22,47 @@ class ByteStringTest extends AbstractAsciiTestCase
|
|||||||
return new ByteString($string);
|
return new ByteString($string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFromRandom(): void
|
||||||
|
{
|
||||||
|
$random = ByteString::fromRandom(32);
|
||||||
|
|
||||||
|
self::assertSame(32, $random->length());
|
||||||
|
foreach ($random->chunk() as $char) {
|
||||||
|
self::assertNotNull(b('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')->indexOf($char));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromRandomWithSpecificChars(): void
|
||||||
|
{
|
||||||
|
$random = ByteString::fromRandom(32, 'abc');
|
||||||
|
|
||||||
|
self::assertSame(32, $random->length());
|
||||||
|
foreach ($random->chunk() as $char) {
|
||||||
|
self::assertNotNull(b('abc')->indexOf($char));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromRandomEarlyReturnForZeroLength(): void
|
||||||
|
{
|
||||||
|
self::assertSame('', ByteString::fromRandom(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromRandomThrowsForNegativeLength(): void
|
||||||
|
{
|
||||||
|
$this->expectException(\RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('Expected positive length value, got -1');
|
||||||
|
|
||||||
|
ByteString::fromRandom(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromRandomAlphabetMin(): void
|
||||||
|
{
|
||||||
|
$this->expectException(\RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('Expected $alphabet\'s length to be in [2^1, 2^56]');
|
||||||
|
|
||||||
|
ByteString::fromRandom(32, 'a');
|
||||||
|
}
|
||||||
|
|
||||||
public static function provideBytesAt(): array
|
public static function provideBytesAt(): array
|
||||||
{
|
{
|
||||||
return array_merge(
|
return array_merge(
|
||||||
|
Reference in New Issue
Block a user