diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 9d6b8d64f5..f6e8155380 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -35,6 +36,13 @@ protected $cacheBackend; /** + * The entity type bundle information object. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $entityTypeBundleInfo; + + /** * Constructs a ContentEntityStorageBase object. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type @@ -43,12 +51,15 @@ * The entity manager. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend to be used. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle information object. */ - public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) { + public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, EntityTypeBundleInfoInterface $entity_type_bundle_info) { parent::__construct($entity_type); $this->bundleKey = $this->entityType->getKey('bundle'); $this->entityManager = $entity_manager; $this->cacheBackend = $cache; + $this->entityTypeBundleInfo = $entity_type_bundle_info; } /** @@ -58,7 +69,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI return new static( $entity_type, $container->get('entity.manager'), - $container->get('cache.entity') + $container->get('cache.entity'), + $container->get('entity_type.bundle.info') ); } @@ -90,6 +102,40 @@ protected function doCreate(array $values) { } /** + * {@inheritdoc} + */ + public function createWithSampleValues($bundle = FALSE, array $values = []) { + // ID and revision should never have sample values generated for them. + $forbidden_keys = [ + $this->entityType->getKey('id'), + ]; + if ($revision_key = $this->entityType->getKey('revision')) { + $forbidden_keys[] = $revision_key; + } + if ($bundle_key = $this->entityType->getKey('bundle')) { + if (!$bundle) { + throw new EntityStorageException("No entity bundle was specified"); + } + if (!array_key_exists($bundle, $this->entityTypeBundleInfo->getBundleInfo($this->entityTypeId))) { + throw new EntityStorageException(sprintf("Missing entity bundle. The \"%s\" bundle does not exist", $bundle)); + } + $values[$bundle_key] = $bundle; + // Bundle is already set + $forbidden_keys[] = $bundle_key; + } + // Forbid sample generation on any keys whose values were submitted. + $forbidden_keys = array_merge($forbidden_keys, array_keys($values)); + /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ + $entity = $this->create($values); + foreach ($entity as $field_name => $value) { + if (!in_array($field_name, $forbidden_keys)) { + $entity->get($field_name)->generateSampleItems(); + } + } + return $entity; + } + + /** * Initializes field values. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php index 47e058d8e1..f851723758 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php @@ -23,4 +23,21 @@ */ public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []); + + /** + * Creates an entity with sample field values. + * + * @param string $bundle + * The entity bundle. + * @param array $values + * (optional) Any default values to use during generation. + * + * @return \Drupal\Core\Entity\FieldableEntityInterface + * A fieldable content entity. + * + * @throws \Drupal\Core\Entity\Exception\MissingEntityBundleException + * Thrown if the requested entity bundle does not exist. + */ + public function createWithSampleValues($bundle, array $values = []); + } diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index 1172406e34..d19c1e184f 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -13,6 +13,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageException; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface; @@ -133,6 +134,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('database'), $container->get('entity.manager'), $container->get('cache.entity'), + $container->get('entity_type.bundle.info'), $container->get('language_manager') ); } @@ -159,11 +161,13 @@ public function getFieldStorageDefinitions() { * The entity manager. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend to be used. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle information object. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) { - parent::__construct($entity_type, $entity_manager, $cache); + public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, EntityTypeBundleInfoInterface $entity_type_bundle_info, LanguageManagerInterface $language_manager) { + parent::__construct($entity_type, $entity_manager, $cache, $entity_type_bundle_info); $this->database = $database; $this->languageManager = $language_manager; $this->initTableLayout(); diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index a1a1ebdb9e..5d13a5e5cc 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -259,7 +259,7 @@ public function view($display_options = []) { */ public function generateSampleItems($count = 1) { $field_definition = $this->getFieldDefinition(); - $field_type_class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_definition->getType()); + $field_type_class = $field_definition->getItemDefinition()->getClass(); for ($delta = 0; $delta < $count; $delta++) { $values[$delta] = $field_type_class::generateSampleValue($field_definition); } diff --git a/core/modules/comment/src/CommentStorage.php b/core/modules/comment/src/CommentStorage.php index d164d8ad9e..fe9c3b1d6a 100644 --- a/core/modules/comment/src/CommentStorage.php +++ b/core/modules/comment/src/CommentStorage.php @@ -5,6 +5,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\FieldableEntityInterface; @@ -41,11 +42,13 @@ class CommentStorage extends SqlContentEntityStorage implements CommentStorageIn * The current user. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * Cache backend instance to use. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle information object. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ - public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) { - parent::__construct($entity_info, $database, $entity_manager, $cache, $language_manager); + public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, EntityTypeBundleInfoInterface $entity_type_bundle_info, LanguageManagerInterface $language_manager) { + parent::__construct($entity_info, $database, $entity_manager, $cache, $entity_type_bundle_info, $language_manager); $this->currentUser = $current_user; } @@ -59,6 +62,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('entity.manager'), $container->get('current_user'), $container->get('cache.entity'), + $container->get('entity_type.bundle.info'), $container->get('language_manager') ); } diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 7529532581..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,6 +499,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->addPropertyConstraints('value', [ 'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedTimezones'], ]); + $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 new file mode 100644 index 0000000000..693d1cc2ac --- /dev/null +++ b/core/modules/user/src/TimeZoneItem.php @@ -0,0 +1,24 @@ +installEntitySchema('user'); + } + + /** * Tests some of the methods. * * @see \Drupal\user\Entity\User::getRoles() @@ -65,4 +73,22 @@ public function testUserMethods() { $this->assertEqual([RoleInterface::AUTHENTICATED_ID, 'test_role_two'], $user->getRoles()); } + /** + * Tests that all user fields validate properly. + * + * @see \Drupal\Core\Field\FieldItemListInterface::generateSampleItems + * @see \Drupal\Core\Field\FieldItemInterface::generateSampleValue() + * @see \Drupal\Core\Entity\FieldableEntityInterface::validate() + */ + public function testUserValidation() { + $user = User::create([]); + foreach ($user as $field_name => $field) { + if (!in_array($field_name, ['uid'])) { + $user->$field_name->generateSampleItems(); + } + } + $violations = $user->validate(); + $this->assertFalse((bool) $violations->count()); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/Element/CreateSampleEntityTest.php b/core/tests/Drupal/KernelTests/Core/Entity/Element/CreateSampleEntityTest.php new file mode 100644 index 0000000000..513266c3d1 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/Element/CreateSampleEntityTest.php @@ -0,0 +1,77 @@ +installEntitySchema('file'); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installEntitySchema('node_type'); + $this->installEntitySchema('file'); + $this->installEntitySchema('comment'); + $this->installEntitySchema('comment_type'); + $this->installEntitySchema('taxonomy_vocabulary'); + $this->installEntitySchema('taxonomy_term'); + $this->entityTypeManager = $this->container->get('entity_type.manager'); + 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 ::createWithSampleValues + */ + public function testGenerateContentEntity() { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $definition) { + if ($definition->entityClassImplements(FieldableEntityInterface::class)) { + // Create sample entities with bundles. + if ($bundle_type = $definition->getBundleEntityType()) { + foreach ($this->entityTypeManager->getStorage($bundle_type)->loadMultiple() as $bundle) { + $entity = $this->entityTypeManager->getStorage($entity_type_id)->createWithSampleValues($bundle->id()); + $violations = $entity->validate(); + $this->assertCount(0, $violations); + } + } + // Create sample entities without bundles. + else { + $entity = $this->entityTypeManager->getStorage($entity_type_id)->createWithSampleValues(); + $violations = $entity->validate(); + $this->assertCount(0, $violations); + } + } + } + } + +}