diff --git a/core/lib/Drupal/Component/Utility/Random.php b/core/lib/Drupal/Component/Utility/Random.php index 16190d8f66949585db85314ea2717eeb5b78b359..799520a78393f2bd3b08614067ee19c439021924 100644 --- a/core/lib/Drupal/Component/Utility/Random.php +++ b/core/lib/Drupal/Component/Utility/Random.php @@ -33,6 +33,13 @@ class Random { */ protected $names = []; + /** + * A list of unique names generated by machineName(). + * + * @var string[] + */ + protected array $machineNames = []; + /** * Generates a random string of ASCII characters of codes 32 to 126. * @@ -130,6 +137,51 @@ public function name($length = 8, $unique = FALSE) { return $str; } + /** + * Generates a string containing lowercase letters and numbers. + * + * This method is used to generate strings that are compliant with Drupal + * machine names. This doesn't include underscores, dashes and periods since + * those are not compatible with all machine names. + * + * @param int $length + * Length of random string to generate. + * @param bool $unique + * If TRUE ensures that the random string returned is unique. + * Defaults to FALSE. + * + * @return string + * Randomly generated string. + * + * @throws \RuntimeException + * Thrown if a unique machine name cannot be generated within the allowed + * number of random attempts. + * + * @see \Drupal\Component\Utility\Random::string() + */ + public function machineName(int $length = 8, bool $unique = FALSE): string { + $values = array_merge(range('a', 'z'), range(0, 9)); + $start_characters = range('a', 'z'); + $counter = 0; + + do { + if ($counter == static::MAXIMUM_TRIES) { + throw new \RuntimeException('Unable to generate a unique random machine name'); + } + $str = $start_characters[array_rand($start_characters)]; + for ($i = 1; $i < $length; $i++) { + $str .= $values[array_rand($values)]; + } + $counter++; + } while ($unique && isset($this->machineNames[$str])); + + if ($unique) { + $this->machineNames[$str] = TRUE; + } + + return $str; + } + /** * Generate a string that looks like a word (letters only, alternating consonants and vowels). * diff --git a/core/tests/Drupal/TestTools/Random.php b/core/tests/Drupal/TestTools/Random.php index 46a4729b0d9fff34feb93afc9074dafbbc83f7b9..35de4a781446ae3056e8ba092b8cde9c1a7c216d 100644 --- a/core/tests/Drupal/TestTools/Random.php +++ b/core/tests/Drupal/TestTools/Random.php @@ -101,7 +101,7 @@ public static function stringValidate(string $string): bool { * @see \Drupal\Component\Utility\Random::name() */ public static function machineName(int $length = 8): string { - return static::getGenerator()->name($length, TRUE); + return static::getGenerator()->machineName($length, TRUE); } /** diff --git a/core/tests/Drupal/Tests/Component/Utility/RandomTest.php b/core/tests/Drupal/Tests/Component/Utility/RandomTest.php index efde521f3effe4ae9894c8f66e3ce25b176340b9..e1682b67f5b7179610e8d152ad8cc6146e02ee0c 100644 --- a/core/tests/Drupal/Tests/Component/Utility/RandomTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/RandomTest.php @@ -115,6 +115,51 @@ public function testRandomStringNonUnique() { $this->assertTrue(TRUE, 'No exception thrown when uniqueness is not enforced.'); } + /** + * Tests unique random name generation. + * + * @covers ::machineName + */ + public function testRandomMachineNamesUniqueness(): void { + $names = []; + $random = new Random(); + for ($i = 0; $i <= 25; $i++) { + $str = $random->machineName(1, TRUE); + $this->assertArrayNotHasKey($str, $names, 'Generated duplicate random name ' . $str); + $names[$str] = TRUE; + } + } + + /** + * Tests infinite loop prevention whilst generating random names. + * + * @covers ::machineName + */ + public function testRandomMachineNameException(): void { + // There are fewer than 100 possibilities so an exception should occur to + // prevent infinite loops. + $this->expectException(\RuntimeException::class); + $random = new Random(); + for ($i = 0; $i <= 100; $i++) { + $random->machineName(1, TRUE); + } + } + + /** + * Tests random name generation if uniqueness is not enforced. + * + * @covers ::machineName + */ + public function testRandomMachineNameNonUnique(): void { + // There are fewer than 100 possibilities meaning if uniqueness was + // enforced, there would be an exception. + $random = new Random(); + for ($i = 0; $i <= 100; $i++) { + $random->machineName(1); + } + $this->expectNotToPerformAssertions(); + } + /** * Tests random object generation to ensure the expected number of properties. *