diff --git a/core/lib/Drupal/Core/Entity/EntityGenerator.php b/core/lib/Drupal/Core/Entity/EntityGenerator.php index 3192d87cc1..62c3791576 100644 --- a/core/lib/Drupal/Core/Entity/EntityGenerator.php +++ b/core/lib/Drupal/Core/Entity/EntityGenerator.php @@ -2,8 +2,9 @@ namespace Drupal\Core\Entity; - use Drupal\Core\Config\Entity\ConfigEntityType; +use Drupal\Core\Entity\Exception\EntityGeneratorMismatchException; +use Drupal\Core\Entity\Exception\MissingEntityGeneratorHandlerException; /** * An entity generator service object. @@ -15,7 +16,7 @@ * * @ingroup entity_generator */ -class EntityGenerator { +class EntityGenerator implements EntityGeneratorInterface { /** * The entity type manager. @@ -25,42 +26,28 @@ class EntityGenerator { protected $entityTypeManager; /** - * EntityGenerate constructor. + * Constructs a new EntityGenerator. * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. */ - public function __construct(EntityTypeManagerInterface $entityTypeManager) { - $this->entityTypeManager = $entityTypeManager; + public function __construct(EntityTypeManagerInterface $entity_type_manager) { + $this->entityTypeManager = $entity_type_manager; } /** - * Generate a fieldable entity. - * - * @param string $entity_type_id - * The entity type id. - * @param string $bundle - * The entity bundle. - * @param array $values - * Any default values to use during generation. - * - * @return \Drupal\Core\Entity\FieldableEntityInterface - * A fieldable content entity. - * - * @throws \Exception - * Thrown if the chosen entity type is not a content entity. + * {@inheritdoc} */ public function generateFieldableEntity($entity_type_id, $bundle, array $values = []) { - $definition = $this->entityTypeManager->getDefinition($entity_type_id); - if (!$definition instanceof ContentEntityType) { - throw new \Exception("Chosen entity type is not fieldable. Use \Drupal\Core\Entity\EntityGenerator::generateConfigurationEntity() instead."); + $entityType = $this->entityTypeManager->getDefinition($entity_type_id); + if (!$entityType->entityClassImplements(FieldableEntityInterface::class)) { + throw new EntityGeneratorMismatchException("Provided entity type \"%s\" is not fieldable. Use \Drupal\Core\Entity\EntityGenerator::generateConfigurationEntity() instead", $entity_type_id); } - $bundle_key = $definition->getKey('bundle'); + $bundle_key = $entityType->getKey('bundle'); $values[$bundle_key] = $bundle; // Allow custom entity generation per entity type. - /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ - if ($definition->hasHandlerClass('generator')) { + if ($entityType->hasHandlerClass('generator')) { /** @var \Drupal\Core\Entity\GeneratorHandlerInterface $generator */ $generator = $this->entityTypeManager->getHandler($entity_type_id, 'generator'); $entity = $generator->generate($values); @@ -68,12 +55,13 @@ public function generateFieldableEntity($entity_type_id, $bundle, array $values else { // Bundle is already set, and id and revision should never be set. $forbidden_keys = [ - $definition->getKey('id'), - $definition->getKey('revision'), + $entityType->getKey('id'), + $entityType->getKey('revision'), $bundle_key ]; $forbidden_keys = array_merge($forbidden_keys, array_keys($values)); // Fallback entity generation in case no custom generator is supplied. + /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ $entity = $this->entityTypeManager->getStorage($entity_type_id)->create($values); foreach ($entity as $field_name => $value) { if (!in_array($field_name, $forbidden_keys)) { @@ -85,22 +73,12 @@ public function generateFieldableEntity($entity_type_id, $bundle, array $values } /** - * Generate a configuration entity. - * - * @param string $entity_type_id - * The entity type id. - * @param array $values - * Any default values to use during generation. - * - * @return \Drupal\Core\Entity\EntityInterface - * The generated entity. - * - * @throws \Exception + * {@inheritdoc} */ public function generateConfigurationEntity($entity_type_id, array $values = []) { $definition = $this->entityTypeManager->getDefinition($entity_type_id); if (!$definition instanceof ConfigEntityType) { - throw new \Exception("Chosen entity type is not a configuration entity. Use \Drupal\Core\Entity\EntityGenerator::generateFieldableEntity() instead."); + throw new EntityGeneratorMismatchException("Provided entity type \"%s\" is not a configuration entity. Use \Drupal\Core\Entity\EntityGenerator::generateFieldableEntity() instead", $entity_type_id); } // Allow custom entity generation per entity type. /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ @@ -110,7 +88,7 @@ public function generateConfigurationEntity($entity_type_id, array $values = []) return $generator->generate($values); } else { - throw new \Exception("Missing generator handler. This entity type cannot be generated."); + throw new MissingEntityGeneratorHandlerException($entity_type_id); } } diff --git a/core/lib/Drupal/Core/Entity/EntityGeneratorInterface.php b/core/lib/Drupal/Core/Entity/EntityGeneratorInterface.php new file mode 100644 index 0000000000..94a81d2997 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityGeneratorInterface.php @@ -0,0 +1,51 @@ + $this->getSampleSentence(1), + 'name' => $this->getSampleSentence(1), + 'description' => $this->getSampleSentence(), + 'help' => $this->getSampleSentence(), + 'new_revision' => $this->getBoolean(), + 'preview_mode' => $this->getPreviewMode(), + 'display_submitted' => $this->getBoolean(), + ]; + $values = array_merge($generated_values, $values); + return NodeType::create($values); + } + + protected function getSampleSentence($words = NULL) { + $random = new Random(); + $words = $words ? $words : mt_rand(10, 20); + $text = ""; + for ($i = 0; $i < $words; $i++) { + $text .= $random->word(mt_rand(2, 7)) . ' '; + } + return $text; + } + + protected function getPreviewMode() { + $options = [ + DRUPAL_DISABLED, + DRUPAL_OPTIONAL, + DRUPAL_REQUIRED, + ]; + $key = rand(0, 2); + return $options[$key]; + } + + protected function getBoolean() { + return (bool) rand(0, 1); + } + +} \ No newline at end of file diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index a294c4d84b..f254ccff93 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -9,6 +9,7 @@ use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Language\LanguageInterface; use Drupal\user\RoleInterface; +use Drupal\user\TimeZoneItem; use Drupal\user\UserInterface; /** @@ -498,7 +499,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->addPropertyConstraints('value', [ 'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedTimezones'], ]); - $fields['timezone']->getItemDefinition()->setClass('\Drupal\user\TimeZoneItem'); + $fields['timezone']->getItemDefinition()->setClass(TimeZoneItem::class); $fields['status'] = BaseFieldDefinition::create('boolean') ->setLabel(t('User status')) diff --git a/core/modules/user/src/TimeZoneItem.php b/core/modules/user/src/TimeZoneItem.php index 0930053adb..d893b115ff 100644 --- a/core/modules/user/src/TimeZoneItem.php +++ b/core/modules/user/src/TimeZoneItem.php @@ -5,6 +5,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\Plugin\Field\FieldType\StringItem; +use Drupal\user\Entity\User; /** * Defines a custom field item class for the 'timezone' user entity field. @@ -15,10 +16,10 @@ class TimeZoneItem extends StringItem { * {@inheritdoc} */ public static function generateSampleValue(FieldDefinitionInterface $field_definition) { - $keys = array_keys(system_time_zones()); - $count = count($keys); - $key = rand(0, $count); - return $keys[$key]; + $timezones = User::getAllowedTimezones(); + // We need to vary the selected timezones since we're generating a sample. + $key = rand(0, count($timezones)); + return $timezones[$key]; } } diff --git a/core/modules/user/src/UserNameItem.php b/core/modules/user/src/UserNameItem.php index 521426f5a1..e6a0f2d420 100644 --- a/core/modules/user/src/UserNameItem.php +++ b/core/modules/user/src/UserNameItem.php @@ -29,7 +29,8 @@ public function isEmpty() { */ public static function generateSampleValue(FieldDefinitionInterface $field_definition) { $values = parent::generateSampleValue($field_definition); - $values['value'] = substr($values['value'], 0, 60); + // User names larger than 60 characters won't pass validation. + $values['value'] = substr($values['value'], 0, UserInterface::USERNAME_MAX_LENGTH); return $values; } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityGeneratorTest.php b/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityGeneratorTest.php index a51935d584..0bd30c1434 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityGeneratorTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityGeneratorTest.php @@ -4,6 +4,8 @@ namespace Drupal\KernelTests\Core\Entity\Element; use Drupal\Core\Entity\ContentEntityType; +use Drupal\Core\Entity\Exception\EntityGeneratorMismatchException; +use Drupal\Core\Entity\Exception\MissingEntityGeneratorHandlerException; use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\NodeType; use Drupal\taxonomy\Entity\Vocabulary; @@ -11,6 +13,7 @@ /** * Tests the EntityGenerator API. * + * @coversDefaultClass \Drupal\Core\Entity\EntityGenerator * @group Entity */ class EntityGeneratorTest extends KernelTestBase { @@ -30,9 +33,7 @@ class EntityGeneratorTest extends KernelTestBase { protected $entityGenerator; /** - * Modules to enable. - * - * @var array + * {@inheritdoc} */ public static $modules = ['system', 'field', 'filter', 'text', 'file', 'user', 'node', 'comment', 'taxonomy']; @@ -53,16 +54,15 @@ protected function setUp() { $this->installEntitySchema('taxonomy_term'); $this->entityTypeManager = $this->container->get('entity_type.manager'); $this->entityGenerator = $this->container->get('entity.generator'); - $node_type = NodeType::create(['type' => 'article', 'name' => 'Article']); - $node_type->save(); - $node_type = NodeType::create(['type' => 'page', 'name' => 'Page']); - $node_type->save(); - $vocabulary = Vocabulary::create(['name' => 'Tags', 'vid' => 'tags']); - $vocabulary->save(); + NodeType::create(['type' => 'article', 'name' => 'Article'])->save(); + NodeType::create(['type' => 'page', 'name' => 'Page'])->save(); + Vocabulary::create(['name' => 'Tags', 'vid' => 'tags'])->save(); } /** * Tests generation of all enabled content entity types. + * + * * @covers ::generateFieldableEntity */ public function testGenerateContentEntity() { foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $definition) { @@ -83,6 +83,47 @@ public function testGenerateContentEntity() { } } } + $this->setExpectedException(EntityGeneratorMismatchException::class, "Provided entity type \"node_type\" is not fieldable. Use \Drupal\Core\Entity\EntityGenerator::generateConfigurationEntity() instead"); + $this->entityGenerator->generateFieldableEntity('node_type', 'page'); + } + + /** + * Tests generation of configuration entity types. + * + * @covers ::generateConfigurationEntity + */ + public function testGenerateConfigEntity() { + /** @var \Drupal\node\Entity\NodeType $node_type */ + $node_type = $this->entityGenerator->generateConfigurationEntity('node_type'); + $this->assertTrue(is_string($node_type->id())); + $this->assertTrue(is_string($node_type->label())); + $this->assertTrue(is_string($node_type->getDescription())); + $this->assertTrue(is_string($node_type->getHelp())); + $this->assertTrue(is_bool($node_type->isNewRevision())); + $this->assertTrue(in_array($node_type->getPreviewMode(), [DRUPAL_DISABLED, DRUPAL_OPTIONAL, DRUPAL_REQUIRED])); + $this->assertTrue(is_bool($node_type->displaySubmitted())); + // Save it and make certain we don't throw an exception. + $this->assertTrue(is_int($node_type->save())); + } + + /** + * Tests mismatched entity generator exception for config entities. + * + * @covers ::generateConfigurationEntity + */ + public function testGeneratorMismatchException() { + $this->setExpectedException(EntityGeneratorMismatchException::class, "Provided entity type \"node\" is not a configuration entity. Use \Drupal\Core\Entity\EntityGenerator::generateFieldableEntity() instead"); + $this->entityGenerator->generateConfigurationEntity('node'); + } + + /** + * Tests missing generator handler entity. + * + * @covers ::generateConfigurationEntity + */ + public function testMissingGeneratorHandlerException() { + $this->setExpectedException(MissingEntityGeneratorHandlerException::class); + $this->entityGenerator->generateConfigurationEntity('taxonomy_vocabulary'); } }