Issue #3091478 by lauriii, Tim Bozeman, malcomio, tim.plunkett, adeshsharma, longwave, EclipseGc, bnjmnm, larowlan, alexpott, amateescu, dpi, quietone: Improve StringItem::generateSampleValue()
parent
ca0a26d23b
commit
036bf79c32
|
@ -72,7 +72,47 @@ class StringItem extends StringItemBase {
|
|||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$random = new Random();
|
||||
$values['value'] = $random->word(mt_rand(1, $field_definition->getSetting('max_length')));
|
||||
$max_length = $field_definition->getSetting('max_length');
|
||||
|
||||
// When the maximum length is less than 15, or the field needs to be unique,
|
||||
// generate a random word using the maximum length.
|
||||
if ($max_length <= 15 || $field_definition->getConstraint('UniqueField')) {
|
||||
$values['value'] = ucfirst($random->word($max_length));
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
// The minimum length is either 10% of the maximum length, or 15 characters
|
||||
// long, whichever is greater.
|
||||
$min_length = max(ceil($max_length * 0.10), 15);
|
||||
|
||||
// Reduce the max length to allow us to add a period.
|
||||
$max_length -= 1;
|
||||
|
||||
// The random value is generated multiple times to create a slight
|
||||
// preference towards values that are closer to the minimum length of the
|
||||
// string. For values larger than 255 (which is the default maximum value),
|
||||
// the bias towards minimum length is increased. This is because the default
|
||||
// maximum length of 255 is often used for fields that include shorter
|
||||
// values (i.e. title).
|
||||
$length = mt_rand($min_length, mt_rand($min_length, $max_length >= 255 ? mt_rand($min_length, $max_length) : $max_length));
|
||||
|
||||
$string = $random->sentences(1);
|
||||
while (mb_strlen($string) < $length) {
|
||||
$string .= " {$random->sentences(1)}";
|
||||
}
|
||||
|
||||
if (mb_strlen($string) > $max_length) {
|
||||
$string = substr($string, 0, $length);
|
||||
$string = substr($string, 0, strrpos($string, ' '));
|
||||
}
|
||||
|
||||
$string = rtrim($string, ' .');
|
||||
|
||||
// Ensure that the string ends with a full stop if there are multiple
|
||||
// sentences.
|
||||
$values['value'] = $string . (str_contains($string, '.') ? '.' : '');
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Field\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
@ -77,8 +78,18 @@ class UriItem extends StringItem {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$values = parent::generateSampleValue($field_definition);
|
||||
$suffix_length = $field_definition->getSetting('max_length') - 7;
|
||||
$random = new Random();
|
||||
|
||||
$max_length = $field_definition->getSetting('max_length');
|
||||
$min_length = min(10, $max_length);
|
||||
|
||||
// The random value is generated multiple times to create a slight
|
||||
// preference towards values that are closer to the minimum length of the
|
||||
// string.
|
||||
$length = mt_rand($min_length, mt_rand($min_length, mt_rand($min_length, $max_length)));
|
||||
$values['value'] = $random->word($length);
|
||||
|
||||
$suffix_length = $max_length - 7;
|
||||
foreach ($values as $key => $value) {
|
||||
$values[$key] = 'http://' . mb_substr($value, 0, $suffix_length);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@ class UniqueFieldConstraint extends Constraint {
|
|||
public $message = 'A @entity_type with @field_name %value already exists.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Returns the name of the class that validates this constraint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function validatedBy() {
|
||||
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
|
||||
|
|
|
@ -12,7 +12,7 @@ use Drupal\Core\Session\AccountSwitcherInterface;
|
|||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\user\UserNameItem;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -158,23 +158,22 @@ class EntityUser extends EntityContentBase {
|
|||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
parent::processStubRow($row);
|
||||
// Email address is not defined as required in the base field definition but
|
||||
// is effectively required by the UserMailRequired constraint. This means
|
||||
// that Entity::processStubRow() did not populate it - we do it here.
|
||||
|
||||
$field_definitions = $this->entityFieldManager
|
||||
->getFieldDefinitions($this->storage->getEntityTypeId(),
|
||||
$this->getKey('bundle'));
|
||||
|
||||
// Name is generated using a dedicated sample value generator to ensure
|
||||
// uniqueness and a valid length.
|
||||
// @todo Remove this as part of https://www.drupal.org/node/3352288.
|
||||
$name = UserNameItem::generateSampleValue($field_definitions['name']);
|
||||
$row->setDestinationProperty('name', reset($name));
|
||||
|
||||
// Email address is not defined as required in the base field definition but
|
||||
// is effectively required by the UserMailRequired constraint. This means
|
||||
// that Entity::processStubRow() did not populate it - we do it here.
|
||||
$mail = EmailItem::generateSampleValue($field_definitions['mail']);
|
||||
$row->setDestinationProperty('mail', reset($mail));
|
||||
|
||||
// @todo Work-around for https://www.drupal.org/node/2602066.
|
||||
$name = $row->getDestinationProperty('name');
|
||||
if (is_array($name)) {
|
||||
$name = reset($name);
|
||||
}
|
||||
if (mb_strlen($name) > UserInterface::USERNAME_MAX_LENGTH) {
|
||||
$row->setDestinationProperty('name', mb_substr($name, 0, UserInterface::USERNAME_MAX_LENGTH));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
|
||||
|
||||
|
@ -28,9 +29,36 @@ class UserNameItem extends StringItem {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$values = parent::generateSampleValue($field_definition);
|
||||
// User names larger than 60 characters won't pass validation.
|
||||
$values['value'] = substr($values['value'], 0, UserInterface::USERNAME_MAX_LENGTH);
|
||||
$random = new Random();
|
||||
$max_length = min(UserInterface::USERNAME_MAX_LENGTH, $field_definition->getSetting('max_length'));
|
||||
|
||||
// Generate a list of words, which can be used to generate a string.
|
||||
$words = explode(' ', $random->sentences(8));
|
||||
|
||||
// Begin with a username that is either 2 or 3 words.
|
||||
$count = mt_rand(2, 3);
|
||||
|
||||
// Capitalize the words used in usernames 50% of the time.
|
||||
$words = mt_rand(0, 1) ? array_map('ucfirst', $words) : $words;
|
||||
|
||||
// Username is a single long word 50% of the time. In the case of a single
|
||||
// long word, sometimes the generated username may also contain periods in
|
||||
// the middle of the username.
|
||||
$separator = ' ';
|
||||
if (mt_rand(0, 1)) {
|
||||
$separator = '';
|
||||
$count = mt_rand(2, 8);
|
||||
|
||||
// The username will start with a capital letter 50% of the time.
|
||||
$words = mt_rand(0, 1) ? array_map('strtolower', $words) : $words;
|
||||
}
|
||||
|
||||
$string = implode($separator, array_splice($words, 0, $count));
|
||||
|
||||
// Normalize the string to not be longer than the maximum length, and to not
|
||||
// end with a space or a period.
|
||||
$values['value'] = rtrim(mb_substr($string, 0, $max_length), ' .');
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\user\Unit;
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\user\UserNameItem;
|
||||
|
||||
/**
|
||||
* Defines a test for the UserNameItem field-type.
|
||||
*
|
||||
* @group Field
|
||||
* @coversDefaultClass \Drupal\user\UserNameItem
|
||||
*/
|
||||
class UserNameItemTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests generating sample values.
|
||||
*
|
||||
* @param int $max_length
|
||||
* Maximum field length.
|
||||
*
|
||||
* @covers ::generateSampleValue
|
||||
* @dataProvider providerMaxLength
|
||||
*/
|
||||
public function testGenerateSampleValue(int $max_length): void {
|
||||
$definition = $this->prophesize(FieldDefinitionInterface::class);
|
||||
$definition->getSetting('max_length')->willReturn($max_length);
|
||||
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$sample_value = UserNameItem::generateSampleValue($definition->reveal());
|
||||
$this->assertLessThanOrEqual($max_length, mb_strlen($sample_value['value']));
|
||||
$this->assertEquals(trim($sample_value['value'], ' '), $sample_value['value']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for maximum-lengths.
|
||||
*
|
||||
* @return array
|
||||
* Test cases.
|
||||
*/
|
||||
public function providerMaxLength(): array {
|
||||
return [
|
||||
'32' => [32],
|
||||
'255' => [255],
|
||||
'500' => [500],
|
||||
'15' => [15],
|
||||
'64' => [64],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Core\Field;
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
|
||||
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Defines a test for the StringItem field-type.
|
||||
*
|
||||
* @group Field
|
||||
* @coversDefaultClass \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
|
||||
*/
|
||||
class StringItemTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests generating sample values.
|
||||
*
|
||||
* @param int $max_length
|
||||
* Maximum field length.
|
||||
*
|
||||
* @covers ::generateSampleValue
|
||||
* @dataProvider providerMaxLength
|
||||
*/
|
||||
public function testGenerateSampleValue(int $max_length): void {
|
||||
foreach ([TRUE, FALSE] as $unique) {
|
||||
$definition = $this->prophesize(FieldDefinitionInterface::class);
|
||||
$constraints = $unique ? [$this->prophesize(UniqueFieldConstraint::class)] : [];
|
||||
$definition->getConstraint('UniqueField')->willReturn($constraints);
|
||||
$definition->getSetting('max_length')->willReturn($max_length);
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$sample_value = StringItem::generateSampleValue($definition->reveal());
|
||||
// When the field value needs to be unique, the generated sample value
|
||||
// should match the maximum length to ensure sufficient entropy.
|
||||
if ($unique) {
|
||||
$this->assertEquals($max_length, mb_strlen($sample_value['value']));
|
||||
}
|
||||
else {
|
||||
$this->assertLessThanOrEqual($max_length, mb_strlen($sample_value['value']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for maximum-lengths.
|
||||
*
|
||||
* @return array
|
||||
* Test cases.
|
||||
*/
|
||||
public function providerMaxLength(): array {
|
||||
return [
|
||||
'32' => [32],
|
||||
'255' => [255],
|
||||
'500' => [500],
|
||||
'15' => [15],
|
||||
'4' => [4],
|
||||
'64' => [64],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Core\Field;
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Defines a test for the UriItem field-type.
|
||||
*
|
||||
* @group Field
|
||||
* @coversDefaultClass \Drupal\Core\Field\Plugin\Field\FieldType\UriItem
|
||||
*/
|
||||
class UriItemTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests generating sample values.
|
||||
*
|
||||
* @param int $max_length
|
||||
* Maximum field length.
|
||||
*
|
||||
* @covers ::generateSampleValue
|
||||
* @dataProvider providerMaxLength
|
||||
*/
|
||||
public function testGenerateSampleValue(int $max_length): void {
|
||||
$definition = $this->prophesize(FieldDefinitionInterface::class);
|
||||
$definition->getSetting('max_length')->willReturn($max_length);
|
||||
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$sample_value = UriItem::generateSampleValue($definition->reveal());
|
||||
$this->assertLessThanOrEqual($max_length, mb_strlen($sample_value['value']));
|
||||
$this->assertStringNotContainsString(' ', $sample_value['value']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for maximum-lengths.
|
||||
*
|
||||
* @return array
|
||||
* Test cases.
|
||||
*/
|
||||
public function providerMaxLength(): array {
|
||||
return [
|
||||
'32' => [32],
|
||||
'255' => [255],
|
||||
'500' => [500],
|
||||
'15' => [15],
|
||||
'64' => [64],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue