feature #36471 [String] allow passing a string of custom characters to ByteString::fromRandom (azjezz)
This PR was merged into the 5.1-dev branch.
Discussion
----------
[String] allow passing a string of custom characters to ByteString::fromRandom
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes/
| Deprecations? | no
| License | MIT
| Doc PR | symfony/symfony-docs#... <!-- required for new features -->
Commits
-------
5d15c0be60
[String] allow passing a string of custom characters to ByteString::fromRandom
This commit is contained in:
commit
5a2aef1d7e
@ -25,20 +25,64 @@ use Symfony\Component\String\Exception\RuntimeException;
|
||||
*/
|
||||
class ByteString extends AbstractString
|
||||
{
|
||||
private const ALPHABET_ALPHANUMERIC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
public function __construct(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 {
|
||||
$string .= str_replace(['/', '+', '='], '', base64_encode(random_bytes($length)));
|
||||
} while (\strlen($string) < $length);
|
||||
$alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
|
||||
$alphabetSize = \strlen($alphabet);
|
||||
$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
|
||||
|
@ -12,6 +12,7 @@ CHANGELOG
|
||||
depending of the input string UTF-8 compliancy
|
||||
* added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()`
|
||||
* added `AbstractString::containsAny()`
|
||||
* allow passing a string of custom characters to `ByteString::fromRandom()`
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\String\Tests;
|
||||
|
||||
use Symfony\Component\String\AbstractString;
|
||||
use function Symfony\Component\String\b;
|
||||
use Symfony\Component\String\ByteString;
|
||||
|
||||
class ByteStringTest extends AbstractAsciiTestCase
|
||||
@ -21,6 +22,47 @@ class ByteStringTest extends AbstractAsciiTestCase
|
||||
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
|
||||
{
|
||||
return array_merge(
|
||||
|
Reference in New Issue
Block a user