diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index b73de24..e16ff3b 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -144,7 +144,7 @@ public function hasKey($key); * Sets a specific entity key. * * @param string $key - * The name of the entity key to return. + * The name of the entity key. * @param string $value * The new value of the key. * diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index 73b88d2..48745d8 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -25,7 +25,7 @@ * "storage" = "Drupal\file\FileStorage", * "access" = "Drupal\file\FileAccessControlHandler", * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", - * "views_data" = "Drupal\file\FileViewsData" + * "views_data" = "Drupal\file\FileViewsData", * }, * base_table = "file_managed", * entity_keys = { diff --git a/core/modules/file/src/FileViewsData.php b/core/modules/file/src/FileViewsData.php index 5c5fee7..f7b1702 100644 --- a/core/modules/file/src/FileViewsData.php +++ b/core/modules/file/src/FileViewsData.php @@ -19,9 +19,7 @@ class FileViewsData extends EntityViewsData { */ public function getViewsData() { $parent_views_data = parent::getViewsData(); - // Sets 'group' index for file_managed table. - // Advertise this table as a possible base table. $data['file_managed']['table']['base'] = array( // @TODO There is no corresponding information in entity metadata. 'help' => t("Files maintained by Drupal and various modules."), diff --git a/core/modules/node/src/NodeViewsData.php b/core/modules/node/src/NodeViewsData.php index 6d69a58..202acc9 100644 --- a/core/modules/node/src/NodeViewsData.php +++ b/core/modules/node/src/NodeViewsData.php @@ -7,45 +7,35 @@ namespace Drupal\node; +use Drupal\Component\Utility\NestedArray; +use Drupal\views\EntityViewsData; use Drupal\views\EntityViewsDataInterface; /** * Provides the views data for the node entity type. */ -class NodeViewsData implements EntityViewsDataInterface { +class NodeViewsData extends EntityViewsData implements EntityViewsDataInterface { /** * {@inheritdoc} */ public function getViewsData() { - // Define the base group of this table. Fields that don't have a group defined - // will go into this field by default. - $data['node']['table']['group'] = t('Content'); + $parent_views_data = parent::getViewsData(); - // Advertise this table as a possible base table. $data['node']['table']['base'] = array( - 'field' => 'nid', - 'title' => t('Content'), 'weight' => -10, 'access query tag' => 'node_access', 'defaults' => array( 'field' => 'title', ), ); - $data['node']['table']['entity type'] = 'node'; $data['node']['table']['wizard_id'] = 'node'; - $data['node_field_data']['table']['group'] = t('Content'); - $data['node_field_data']['table']['entity type'] = 'node'; $data['node_field_data']['table']['join']['node'] = array( 'type' => 'INNER', - 'left_field' => 'nid', - 'field' => 'nid', ); $data['node']['nid'] = array( - 'title' => t('Nid'), - 'help' => t('The node ID.'), 'field' => array( 'id' => 'node', ), @@ -55,18 +45,10 @@ public function getViewsData() { 'numeric' => TRUE, 'validate type' => 'nid', ), - 'filter' => array( - 'id' => 'numeric', - ), - 'sort' => array( - 'id' => 'standard', - ), ); // This definition has more items in it than it needs to as an example. $data['node_field_data']['title'] = array( - 'title' => t('Title'), - 'help' => t('The content title.'), 'field' => array( // This is the real field which could be left out since it is the same. 'field' => 'title', @@ -75,54 +57,12 @@ public function getViewsData() { 'id' => 'node', 'link_to_node default' => TRUE, ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'string', - ), - 'argument' => array( - 'id' => 'string', - ), - ); - - $data['node_field_data']['created'] = array( - 'title' => t('Post date'), - 'help' => t('The date the content was posted.'), - 'field' => array( - 'id' => 'date', - ), - 'sort' => array( - 'id' => 'date' - ), - 'filter' => array( - 'id' => 'date', - ), - ); - - $data['node_field_data']['changed'] = array( - 'title' => t('Updated date'), - 'help' => t('The date the content was last updated.'), - 'field' => array( - 'id' => 'date', - ), - 'sort' => array( - 'id' => 'date' - ), - 'filter' => array( - 'id' => 'date', - ), ); $data['node_field_data']['type'] = array( - 'title' => t('Type'), - 'help' => t('The content type (for example, "blog entry", "forum post", "story", etc).'), 'field' => array( 'id' => 'node_type', ), - 'sort' => array( - 'id' => 'standard', - ), 'filter' => array( 'id' => 'bundle', ), @@ -133,42 +73,25 @@ public function getViewsData() { if (\Drupal::moduleHandler()->moduleExists('language')) { $data['node_field_data']['langcode'] = array( - 'title' => t('Translation language'), 'help' => t('The language of the content or translation.'), 'field' => array( 'id' => 'node_language', ), - 'filter' => array( - 'id' => 'language', - ), - 'argument' => array( - 'id' => 'language', - ), - 'sort' => array( - 'id' => 'standard', - ), ); } $data['node_field_data']['status'] = array( - 'title' => t('Published status'), - 'help' => t('Whether or not the content is published.'), 'field' => array( - 'id' => 'boolean', 'output formats' => array( 'published-notpublished' => array(t('Published'), t('Not published')), ), ), 'filter' => array( - 'id' => 'boolean', 'label' => t('Published status'), 'type' => 'yes-no', // Use status = 1 instead of status <> 0 in WHERE statement. 'use_equal' => TRUE, ), - 'sort' => array( - 'id' => 'standard', - ), ); $data['node_field_data']['status_extra'] = array( @@ -182,40 +105,28 @@ public function getViewsData() { ); $data['node_field_data']['promote'] = array( - 'title' => t('Promoted to front page status'), - 'help' => t('Whether or not the content is promoted to the front page.'), 'field' => array( - 'id' => 'boolean', 'output formats' => array( 'promoted-notpromoted' => array(t('Promoted'), t('Not promoted')), ), ), 'filter' => array( - 'id' => 'boolean', 'label' => t('Promoted to front page status'), 'type' => 'yes-no', ), - 'sort' => array( - 'id' => 'standard', - ), ); $data['node_field_data']['sticky'] = array( - 'title' => t('Sticky status'), - 'help' => t('Whether or not the content is sticky.'), 'field' => array( - 'id' => 'boolean', 'output formats' => array( 'sticky' => array(t('Sticky'), t('Not sticky')), ), ), 'filter' => array( - 'id' => 'boolean', 'label' => t('Sticky status'), 'type' => 'yes-no', ), 'sort' => array( - 'id' => 'standard', 'help' => t('Whether or not the content is sticky. To list sticky content first, set this to descending.'), ), ); @@ -264,6 +175,7 @@ public function getViewsData() { // Bogus fields for aliasing purposes. + // @Todo Add similar support to any date field. $data['node_field_data']['created_fulldate'] = array( 'title' => t('Created date'), 'help' => t('Date in the form of CCYYMMDD.'), @@ -373,22 +285,15 @@ public function getViewsData() { ); $data['node_field_data']['uid'] = array( - 'title' => t('Author uid'), 'help' => t('The user authoring the content. If you need more fields than the uid add the content: author relationship'), 'relationship' => array( 'title' => t('Content author'), 'help' => t('Relate content to the user who created it.'), - 'id' => 'standard', - 'base' => 'users', - 'field' => 'uid', 'label' => t('author'), ), 'filter' => array( 'id' => 'user_name', ), - 'argument' => array( - 'id' => 'numeric', - ), 'field' => array( 'id' => 'user', ), @@ -414,46 +319,24 @@ public function getViewsData() { ), ); - $data['node_revision']['table']['entity type'] = 'node'; - // Define the base group of this table. Fields that don't have a group defined - // will go into this field by default. - $data['node_revision']['table']['group'] = t('Content revision'); $data['node_revision']['table']['wizard_id'] = 'node_revision'; // Advertise this table as a possible base table. $data['node_revision']['table']['base'] = array( - 'field' => 'vid', - 'title' => t('Content revision'), 'help' => t('Content revision is a history of changes to content.'), 'defaults' => array( 'field' => 'title', ), ); - // For other base tables, explain how we join. - $data['node_revision']['table']['join'] = array( - 'node' => array( - 'left_field' => 'vid', - 'field' => 'vid', - ), - ); - $data['node_revision']['nid'] = array( - 'title' => t('Nid'), - 'help' => t('The revision NID of the content revision.'), - 'field' => array( - 'id' => 'standard', - ), 'argument' => array( 'id' => 'node_nid', 'numeric' => TRUE, ), - 'filter' => array( - 'id' => 'numeric', - ), - 'sort' => array( - 'id' => 'standard', - ), + // @todo the NID field needs different behaviour on revision/non-revision + // tables. It would be neat if this could be encoded in the base field + // definition. 'relationship' => array( 'id' => 'standard', 'base' => 'node', @@ -464,21 +347,10 @@ public function getViewsData() { ); $data['node_revision']['vid'] = array( - 'title' => t('Vid'), - 'help' => t('The revision ID of the content revision.'), - 'field' => array( - 'id' => 'standard', - ), 'argument' => array( 'id' => 'node_vid', 'numeric' => TRUE, ), - 'filter' => array( - 'id' => 'numeric', - ), - 'sort' => array( - 'id' => 'standard', - ), 'relationship' => array( 'id' => 'standard', 'base' => 'node', @@ -490,49 +362,26 @@ public function getViewsData() { if (\Drupal::moduleHandler()->moduleExists('language')) { $data['node_revision']['langcode'] = array( - 'title' => t('Original language'), 'help' => t('The language the original content is in.'), 'field' => array( 'id' => 'node_language', ), - 'filter' => array( - 'id' => 'language', - ), - 'argument' => array( - 'id' => 'language', - ), - 'sort' => array( - 'id' => 'standard', - ), ); } $data['node_revision']['revision_log'] = array( - 'title' => t('Log message'), - 'help' => t('The log message entered when the revision was created.'), 'field' => array( 'id' => 'xss', ), - 'filter' => array( - 'id' => 'string', - ), ); $data['node_revision']['revision_uid'] = array( - 'title' => t('User'), 'help' => t('Relate a content revision to the user who created the revision.'), 'relationship' => array( - 'id' => 'standard', - 'base' => 'users', - 'base field' => 'uid', 'label' => t('revision user'), ), ); - $data['node_field_revision']['table']['entity type'] = 'node'; - // Define the base group of this table. Fields that don't have a group defined - // will go into this field by default. - $data['node_field_revision']['table']['group'] = t('Content revision'); $data['node_field_revision']['table']['wizard_id'] = 'node_field_revision'; // For other base tables, explain how we join. @@ -541,63 +390,27 @@ public function getViewsData() { 'left_field' => 'vid', 'field' => 'vid', ), - 'node_revision' => array( - 'left_field' => 'vid', - 'field' => 'vid', - ), ); $data['node_field_revision']['status'] = array( - 'title' => t('Published'), - 'help' => t('Whether or not the content is published.'), 'field' => array( - 'id' => 'boolean', 'output formats' => array( 'published-notpublished' => array(t('Published'), t('Not published')), ), ), 'filter' => array( - 'id' => 'boolean', 'label' => t('Published'), 'type' => 'yes-no', // Use status = 1 instead of status <> 0 in WHERE statement. 'use_equal' => TRUE, ), - 'sort' => array( - 'id' => 'standard', - ), ); $data['node_field_revision']['title'] = array( - 'title' => t('Title'), - 'help' => t('The content title.'), 'field' => array( 'field' => 'title', 'id' => 'node_revision', ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'string', - ), - 'argument' => array( - 'id' => 'string', - ), - ); - - $data['node_field_revision']['changed'] = array( - 'title' => t('Updated date'), - 'help' => t('The date the content was last updated.'), - 'field' => array( - 'id' => 'date', - ), - 'sort' => array( - 'id' => 'date' - ), - 'filter' => array( - 'id' => 'date', - ), ); $data['node_revision']['link_to_revision'] = array( @@ -724,9 +537,7 @@ public function getViewsData() { } } - return $data; + return NestedArray::mergeDeep($parent_views_data, $data); } - } - diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php index 56505c2..84a5e99 100644 --- a/core/modules/system/entity.api.php +++ b/core/modules/system/entity.api.php @@ -310,7 +310,8 @@ * config entities will use \Drupal\Core\Config\Entity\ConfigEntityStorage. * You can extend one of these classes to provide custom behavior. * - views_data: A class implementing \Drupal\views\EntityViewsDataInterface - * to provide views data for the entity type. + * to provide views data for the entity type. The class can use + * \Drupal\views\EntityViewsData to autogenerate most of the views data. * - For content entities, the annotation will refer to a number of database * tables and their fields. These annotation properties, such as 'base_table', * 'data_table', 'entity_keys', etc., are documented on diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php index 3533964..cc89524 100644 --- a/core/modules/views/src/EntityViewsData.php +++ b/core/modules/views/src/EntityViewsData.php @@ -34,11 +34,11 @@ class EntityViewsData implements EntityControllerInterface, EntityViewsDataInter protected $entityType; /** - * The storage controller used for this entity type. + * The storage used for this entity type. * * @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface */ - protected $storageController; + protected $storage; /** * The module handler. @@ -85,7 +85,7 @@ class EntityViewsData implements EntityControllerInterface, EntityViewsDataInter function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager, TypedDataManager $typed_data_manager) { $this->entityType = $entity_type; $this->entityManager = $entity_manager; - $this->storageController = $storage_controller; + $this->storage = $storage_controller; $this->moduleHandler = $module_handler; $this->setStringTranslation($translation_manager); $this->typedDataManager = $typed_data_manager; @@ -121,7 +121,7 @@ protected function getFieldStorageDefinitions() { * {@inheritdoc} */ public function getViewsData() { - $data = array(); + $data = []; // @todo In theory we should use the data table as base table, as this would // save one pointless join (and one more for every relationship). @@ -135,33 +135,37 @@ public function getViewsData() { // Setup base information of the views data. $data[$base_table]['table']['entity type'] = $this->entityType->id(); $data[$base_table]['table']['group'] = $this->entityType->getLabel(); - $data[$base_table]['table']['base'] = array( + $data[$base_table]['table']['base'] = [ 'field' => $base_field, 'title' => $this->entityType->getLabel(), - ); + ]; // Setup relations to the revisions/property data. if ($data_table) { - $data[$data_table]['table']['join'][$base_table] = array( + $data[$data_table]['table']['join'][$base_table] = [ 'left_field' => $base_field, - 'field' => $base_field - ); - + 'field' => $base_field, + ]; + $data[$data_table]['table']['entity type'] = $this->entityType->id(); + $data[$data_table]['table']['group'] = $this->entityType->getLabel(); } if ($revision_table) { - $data[$revision_table]['table']['entity_type_id'] = $this->entityType->id(); - $data[$revision_table]['table']['group'] = $this->entityType->getLabel(); + $data[$revision_table]['table']['entity type'] = $this->entityType->id(); + $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); $data[$revision_table]['table']['base'] = array( 'field' => $revision_field, 'title' => $this->t('@entity_type revisions', array('@entity_type' => $this->entityType->getLabel())), ); // Join the revision table to the base table. $data[$revision_table]['table']['join'][$base_table] = array( - 'left_field' => $base_field, - 'field' => $base_field, + 'left_field' => $revision_field, + 'field' => $revision_field, ); if ($revision_data_table) { + $data[$revision_data_table]['table']['entity type'] = $this->entityType->id(); + $data[$revision_data_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); + $data[$revision_data_table]['table']['join'][$revision_table] = array( 'left_field' => $revision_field, 'field' => $revision_field, @@ -172,7 +176,7 @@ public function getViewsData() { // Load all typed data definitions of all fields. This should cover each of // the entity base, revision, data tables. $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id()); - if ($table_mapping = $this->storageController->getTableMapping()) { + if ($table_mapping = $this->storage->getTableMapping()) { // Iterate over each table we have so far and collect field data for each. // Based on whether the field is in the field_definitions provided by the // entity manager. @@ -180,7 +184,7 @@ public function getViewsData() { // storage. foreach ($table_mapping->getTableNames() as $table) { foreach ($table_mapping->getFieldNames($table) as $field_name) { - $this->mapFieldDefinition($field_name, $field_definitions[$field_name], $table_mapping, $data[$table]); + $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]); } } } @@ -188,7 +192,20 @@ public function getViewsData() { return $data; } - protected function mapFieldDefinition($field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$views_table_data) { + /** + * Puts the views data for a single field onto the views data. + * + * @param string $field_name + * The name of the field to handle. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition defined in Entity::baseFieldDefinitions() + * @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping + * The table mapping information + * @param array $table_data + * A reference to a specific entity table (for example data_table) inside + * the views data. + */ + protected function mapFieldDefinition($table, $field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$table_data) { // Create a dummy instance to retrieve property definitions. $field_column_mapping = $table_mapping->getColumnNames($field_name); $field_schema = $this->getFieldStorageDefinitions()[$field_name]->getSchema(); @@ -198,26 +215,45 @@ protected function mapFieldDefinition($field_name, FieldDefinitionInterface $fie $first = TRUE; foreach ($field_column_mapping as $field_column_name => $schema_field_name) { $schema = $field_schema['columns'][$field_column_name]; + // We want to both have an entry in the views data for the actual field, + // but also each additional schema field, for example the file + // description. + // @todo Introduce a concept of the "main" schema field for a field item. + // This would be the FID for a file reference for example. if ($first) { $first = FALSE; - $views_table_data[$field_name] = $this->mapSingleFieldViewsData($field_definition_type, $schema_field_name, $field_definition); + $table_data[$field_name] = $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $schema_field_name, $field_definition, TRUE); } else { - $views_table_data["$field_name.$field_column_name"] = $this->mapSingleFieldViewsData($schema['type'], $schema_field_name, $field_definition); + $table_data["$field_name.$field_column_name"] = $this->mapSingleFieldViewsData($table, $field_name, $schema['type'], $schema_field_name, $field_definition, FALSE); } } } /** - * Provides a mapping from typed data plugin types to view plugin types. + * Provides the views data for a given data type and schema field. + * + * @param string $data_type + * The data type to generate views data for, for example "int". The data + * type comes directly from the schema definition of each field item. + * @param string $schema_field_name + * The schema field name. + * @param bool $first + * Is it the first column of the schema. * * @return array * The modified views data field definition. */ - protected function mapSingleFieldViewsData($data_type, $schema_field_name, FieldDefinitionInterface $field_definition) { + protected function mapSingleFieldViewsData($table, $field_name, $data_type, $schema_field_name, FieldDefinitionInterface $field_definition, $first) { $views_field = array(); - $views_field['title'] = $field_definition->getLabel() . " ($schema_field_name)"; + // Provide a nicer, less verbose label for the first field. + if ($first) { + $views_field['title'] = $field_definition->getLabel(); + } + else { + $views_field['title'] = $field_definition->getLabel() . " ($schema_field_name)"; + } if ($description = $field_definition->getDescription()) { $views_field['help'] = $description; @@ -286,13 +322,13 @@ protected function mapSingleFieldViewsData($data_type, $schema_field_name, Field $views_field['sort']['id'] = 'standard'; if ($entity_type_id = $field_definition->getItemDefinition()->getSetting('target_type')) { $entity_type = $this->entityManager->getDefinition($entity_type_id); - $views_field['relationship'] = array( + $views_field['relationship'] = [ 'base' => $this->getViewsTableForEntityType($entity_type), 'base field' => $entity_type->getKey('id'), 'label' => $entity_type->getLabel(), 'title' => $entity_type->getLabel(), 'id' => 'standard', - ); + ]; } break; case 'uri': @@ -308,10 +344,40 @@ protected function mapSingleFieldViewsData($data_type, $schema_field_name, Field $views_field['sort']['id'] = 'standard'; } + $process_method = "processViewsDataFor$data_type"; + if (method_exists($this, $process_method)) { + $this->{$process_method}($table, $field_name, $views_field); + } + return $views_field; } - public function getViewsTableForEntityType(EntityTypeInterface $entity_type) { + protected function processViewsDataForLanguage($table, $field_name, array &$views_field) { + // Apply special titles for the langcode field. + if ($field_name == 'langcode') { + if ($table == $this->entityType->getDataTable() || $table == $this->entityType->getBaseTable()) { + $views_field['title'] = $this->t('Translation language'); + } + if ($table == $this->entityType->getRevisionDataTable() || $table == $this->entityType->getRevisionTable()) { + $views_field['title'] = $this->t('Original language'); + } + } + } + + /** + * Gets the table of an entity type to be used as base table in views. + * + * @todo Given that the base_table is pretty much useless as you often have to + * join to the data table anyway, it could make a lot of sense to start with + * the data table right from the beginning. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type + * + * @return string + * THe name of the base table in views. + */ + protected function getViewsTableForEntityType(EntityTypeInterface $entity_type) { return $entity_type->getBaseTable(); } diff --git a/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php b/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php index 5f0a8c7..7bbea2f 100644 --- a/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php +++ b/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php @@ -71,6 +71,9 @@ class EntityViewsDataTest extends UnitTestCase { protected $viewsData; protected $typedDataManager; + /** + * {@inheritdoc} + */ protected function setUp() { $this->entityStorage = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityDatabaseStorage') ->disableOriginalConstructor() @@ -139,6 +142,9 @@ public function testDataTable() { $data = $this->viewsData->getViewsData(); $field_views_data = $data['entity_test_mul_property_data']; + $this->assertEquals('entity_test_mul', $data['entity_test_mul_property_data']['table']['entity type']); + $this->assertEquals('Entity test', $data['entity_test_mul_property_data']['table']['group']); + // Ensure the join information is set up properly. $this->assertCount(1, $field_views_data['table']['join']); $this->assertEquals(['entity_test' => ['left_field' => 'id', 'field' => 'id']], $field_views_data['table']['join']); @@ -160,11 +166,15 @@ public function testRevisionTable() { $data = $this->viewsData->getViewsData(); + $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_revision']['table']['entity type']); + $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_property_revision']['table']['entity type']); + $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']); + // Ensure the join information is set up properly. // Tests the join definition between the base and the revision table. $revision_data = $data['entity_test_mulrev_revision']; $this->assertCount(1, $revision_data['table']['join']); - $this->assertEquals(['entity_test' => ['left_field' => 'id', 'field' => 'id']], $revision_data['table']['join']); + $this->assertEquals(['entity_test' => ['left_field' => 'revision_id', 'field' => 'revision_id']], $revision_data['table']['join']); $revision_data = $data['entity_test_mulrev_property_revision']; $this->assertCount(1, $revision_data['table']['join']); $this->assertEquals(['entity_test_mulrev_revision' => ['left_field' => 'revision_id', 'field' => 'revision_id']], $revision_data['table']['join']); @@ -273,7 +283,10 @@ public function testBaseTableFields() { $this->assertNumericField($data['entity_test']['id']); $this->assertUuidField($data['entity_test']['uuid']); $this->assertStringField($data['entity_test']['type']); + $this->assertLanguageField($data['entity_test']['langcode']); + $this->assertEquals('Translation language', $data['entity_test']['langcode']['title']); + $this->assertStringField($data['entity_test']['name']); $this->assertEntityReferenceField($data['entity_test']['user_id']); @@ -345,7 +358,10 @@ public function testDataTableFields() { // Check the data fields. $this->assertNumericField($data['entity_test_mul_property_data']['id']); + $this->assertLanguageField($data['entity_test_mul_property_data']['langcode']); + $this->assertEquals('Translation language', $data['entity_test_mul_property_data']['langcode']['title']); + $this->assertStringField($data['entity_test_mul_property_data']['name']); $this->assertEntityReferenceField($data['entity_test_mul_property_data']['user_id']); @@ -427,7 +443,10 @@ public function testRevisionTableFields() { // Check the revision fields. $this->assertNumericField($data['entity_test_mulrev_revision']['id']); $this->assertNumericField($data['entity_test_mulrev_revision']['revision_id']); + $this->assertLanguageField($data['entity_test_mulrev_revision']['langcode']); + $this->assertEquals('Original language', $data['entity_test_mulrev_revision']['langcode']['title']); + // Also ensure that field_data only fields don't appear on the revision table. $this->assertFalse(isset($data['entity_test_mulrev_revision']['name'])); $this->assertFalse(isset($data['entity_test_mulrev_revision']['user_id'])); @@ -444,7 +463,10 @@ public function testRevisionTableFields() { // Check the property data fields. $this->assertNumericField($data['entity_test_mulrev_property_revision']['id']); + $this->assertLanguageField($data['entity_test_mulrev_property_revision']['langcode']); + $this->assertEquals('Original language', $data['entity_test_mulrev_property_revision']['langcode']['title']); + $this->assertStringField($data['entity_test_mulrev_property_revision']['name']); $this->assertEntityReferenceField($data['entity_test_mulrev_property_revision']['user_id']); @@ -459,7 +481,7 @@ public function testRevisionTableFields() { * @param $data * The views data to check. */ - public function assertStringField($data) { + protected function assertStringField($data) { $this->assertEquals('standard', $data['field']['id']); $this->assertEquals('string', $data['filter']['id']); $this->assertEquals('string', $data['argument']['id']); @@ -472,7 +494,7 @@ public function assertStringField($data) { * @param array $data * The views data to check. */ - public function assertUuidField($data) { + protected function assertUuidField($data) { // @todo Can we provide additional support for UUIDs in views? $this->assertEquals('standard', $data['field']['id']); $this->assertEquals('string', $data['filter']['id']); @@ -486,7 +508,7 @@ public function assertUuidField($data) { * @param array $data * The views data to check. */ - public function assertNumericField($data) { + protected function assertNumericField($data) { $this->assertEquals('numeric', $data['field']['id']); $this->assertEquals('numeric', $data['filter']['id']); $this->assertEquals('numeric', $data['argument']['id']); @@ -499,14 +521,17 @@ public function assertNumericField($data) { * @param array $data * The views data to check. */ - public function assertLanguageField($data) { + protected function assertLanguageField($data) { $this->assertEquals('language', $data['field']['id']); $this->assertEquals('language', $data['filter']['id']); $this->assertEquals('language', $data['argument']['id']); $this->assertEquals('standard', $data['sort']['id']); } - public function assertEntityReferenceField($data) { + /** + * Tests views data for a entity reference field. + */ + protected function assertEntityReferenceField($data) { $this->assertEquals('numeric', $data['field']['id']); $this->assertEquals('numeric', $data['filter']['id']); $this->assertEquals('numeric', $data['argument']['id']); @@ -518,7 +543,7 @@ public function assertEntityReferenceField($data) { * * @return array */ - public static function userEntityInfo() { + protected static function userEntityInfo() { return new ContentEntityType([ 'id' => 'user', 'class' => 'Drupal\user\Entity\User', diff --git a/core/modules/views/views.api.php b/core/modules/views/views.api.php index e5c8c50..c90d0ea 100644 --- a/core/modules/views/views.api.php +++ b/core/modules/views/views.api.php @@ -26,9 +26,10 @@ * by implementing hook_views_data_alter(). To provide views data for an * entity, create a class implementing * \Drupal\views\EntityViewsDataInterface and reference this in the - * "views_data" annotation in the entity class. See the - * @link entity_api Entity API topic @endlink for more information about - * entities. + * "views_data" annotation in the entity class. You can autogenerate big parts + * of the ingration if you extend the \Drupal\views\EntityViewsData base + * class. See the @link entity_api Entity API topic @endlink for more + * information about entities. * - Implement hooks: A few operations in Views can be influenced by hooks. * See the @link Views hooks topic @endlink for a list. * - Theming: See the @link views_templates Views templates topic @endlink diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php index eb6d936..8033286 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php @@ -76,6 +76,19 @@ public function providerTestGetKeys() { } /** + * Tests the setKey() method. + * + * @covers ::setKey + */ + public function testSetKey() { + $entity_type = $this->setUpEntityType([]); + + $this->assertFalse($entity_type->getKey('id')); + $this->assertSame($entity_type, $entity_type->setKey('id', 'new_value')); + $this->assertEquals('new_value', $entity_type->getKey('id')); + } + + /** * Tests the isRevisionable() method. */ public function testIsRevisionable() {