From 445bc901753167d8a71b0bf9fa9a303ad8ad64a3 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole Date: Mon, 6 Jun 2016 12:20:41 +0100 Subject: [PATCH] Issue #2477899 by dawehner, kevin.dutra, borisson_, bojanz, catch, amateescu: Multiple valued Base fields won't work in Views --- .../Entity/EntityTestMultiValueBasefield.php | 39 ++++++ core/modules/views/src/EntityViewsData.php | 39 ++++++ ....view.test_entity_multivalue_basefield.yml | 45 ++++++ ...EntityViewsWithMultivalueBasefieldTest.php | 54 ++++++++ .../tests/src/Unit/EntityViewsDataTest.php | 131 +++++++++++++++++- core/modules/views/views.install | 7 + 6 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml create mode 100644 core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php new file mode 100644 index 00000000000..b5bf2c2526d --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php @@ -0,0 +1,39 @@ +setCardinality(2); + + return $fields; + } + +} diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php index cc7b7be9013..61def5acd49 100644 --- a/core/modules/views/src/EntityViewsData.php +++ b/core/modules/views/src/EntityViewsData.php @@ -57,6 +57,13 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac */ protected $fieldStorageDefinitions; + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + /** * Constructs an EntityViewsData object. * @@ -112,6 +119,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac $data = []; $base_table = $this->entityType->getBaseTable() ?: $this->entityType->id(); + $views_revision_base_table = NULL; $revisionable = $this->entityType->isRevisionable(); $base_field = $this->entityType->getKey('id'); @@ -235,6 +243,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac // 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()); + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ if ($table_mapping = $this->storage->getTableMapping($field_definitions)) { // Fetch all fields that can appear in both the base table and the data // table. @@ -257,6 +266,36 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]); } } + + foreach ($field_definitions as $field_definition) { + if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) { + $table = $table_mapping->getDedicatedDataTableName($field_definition->getFieldStorageDefinition()); + + $data[$table]['table']['group'] = $this->entityType->getLabel(); + $data[$table]['table']['provider'] = $this->entityType->getProvider(); + $data[$table]['table']['join'][$views_base_table] = [ + 'left_field' => $base_field, + 'field' => 'entity_id', + 'extra' => [ + ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], + ], + ]; + + if ($revisionable) { + $revision_table = $table_mapping->getDedicatedRevisionTableName($field_definition->getFieldStorageDefinition()); + + $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); + $data[$revision_table]['table']['provider'] = $this->entityType->getProvider(); + $data[$revision_table]['table']['join'][$views_revision_base_table] = [ + 'left_field' => $revision_field, + 'field' => 'entity_id', + 'extra' => [ + ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], + ], + ]; + } + } + } } // Add the entity type key to each table generated. diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml new file mode 100644 index 00000000000..478071dfa7c --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml @@ -0,0 +1,45 @@ +langcode: en +status: true +dependencies: + module: + - entity_test +id: test_entity_multivalue_basefield +label: '' +module: views +description: '' +tag: '' +base_table: entity_test_multivalue_basefield +base_field: id +core: '8' +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + fields: + id: + id: id + table: entity_test_multivalue_basefield + field: nid + relationship: none + plugin_id: field + entity_type: entity_test_multivalue_basefield + entity_field: id + name: + id: name + table: entity_test_multivalue_basefield__name + field: name + plugin_id: field + entity_type: entity_test_multivalue_basefield + entity_field: name + defaults: + fields: false + filters: false + sorts: + id: + id: id + table: entity_test_multivalue_basefield + field: id + order: asc diff --git a/core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php b/core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php new file mode 100644 index 00000000000..edf0fb0e10b --- /dev/null +++ b/core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php @@ -0,0 +1,54 @@ +installEntitySchema('entity_test_multivalue_basefield'); + } + + /** + * Tests entity views with multivalue base fields. + */ + public function testView() { + EntityTestMultiValueBasefield::create([ + 'name' => 'test', + ])->save(); + EntityTestMultiValueBasefield::create([ + 'name' => ['test2', 'test3'], + ])->save(); + + $view = Views::getView('test_entity_multivalue_basefield'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['name' => ['test']], + ['name' => ['test2', 'test3']], + ], ['name' => 'name']); + } + +} diff --git a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php index c1db0cfeee5..245b8f1692e 100644 --- a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php +++ b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\Core\Entity\ContentEntityType; use Drupal\Core\Entity\EntityType; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\Sql\DefaultTableMapping; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem; @@ -160,6 +161,12 @@ class EntityViewsDataTest extends UnitTestCase { ->setTranslatable(TRUE) ->setSetting('max_length', 255); + // A base field with cardinality > 1 + $base_fields['string'] = BaseFieldDefinition::create('string') + ->setLabel('Strong') + ->setTranslatable(TRUE) + ->setCardinality(2); + foreach ($base_fields as $name => $base_field) { $base_field->setName($name); } @@ -376,6 +383,10 @@ class EntityViewsDataTest extends UnitTestCase { $homepage_field_storage_definition->expects($this->any()) ->method('getSchema') ->willReturn(UriItem::schema($homepage_field_storage_definition)); + $string_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $string_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(StringItem::schema($string_field_storage_definition)); // Setup the user_id entity reference field. $this->entityManager->expects($this->any()) @@ -411,6 +422,7 @@ class EntityViewsDataTest extends UnitTestCase { 'name' => $name_field_storage_definition, 'description' => $description_field_storage_definition, 'homepage' => $homepage_field_storage_definition, + 'string' => $string_field_storage_definition, 'user_id' => $user_id_field_storage_definition, 'revision_id' => $revision_id_field_storage_definition, ]); @@ -435,10 +447,12 @@ class EntityViewsDataTest extends UnitTestCase { ['entity_test', $base_field_definitions], ])); // Setup the table mapping. - $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) + ->disableOriginalConstructor() + ->getMock(); $table_mapping->expects($this->any()) ->method('getTableNames') - ->willReturn(['entity_test']); + ->willReturn(['entity_test', 'entity_test__string']); $table_mapping->expects($this->any()) ->method('getColumnNames') ->willReturnMap([ @@ -450,12 +464,26 @@ class EntityViewsDataTest extends UnitTestCase { ['description', ['value' => 'description__value', 'format' => 'description__format']], ['homepage', ['value' => 'homepage']], ['user_id', ['target_id' => 'user_id']], + ['string', ['value' => 'value']], ]); $table_mapping->expects($this->any()) ->method('getFieldNames') ->willReturnMap([ - ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']] + ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']], + ['entity_test__string', ['string']], ]); + $table_mapping->expects($this->any()) + ->method('requiresDedicatedTableStorage') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + return $base_field->getName() === 'string'; + }); + $table_mapping->expects($this->any()) + ->method('getDedicatedDataTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test__string'; + } + }); $this->entityStorage->expects($this->once()) ->method('getTableMapping') @@ -492,6 +520,18 @@ class EntityViewsDataTest extends UnitTestCase { $relationship = $data['entity_test']['user_id']['relationship']; $this->assertEquals('users_field_data', $relationship['base']); $this->assertEquals('uid', $relationship['base field']); + + $this->assertStringField($data['entity_test__string']['string']); + $this->assertField($data['entity_test__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test__string']['table']['join']['entity_test']); } /** @@ -529,10 +569,12 @@ class EntityViewsDataTest extends UnitTestCase { $this->viewsData->setEntityType($entity_type); // Setup the table mapping. - $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) + ->disableOriginalConstructor() + ->getMock(); $table_mapping->expects($this->any()) ->method('getTableNames') - ->willReturn(['entity_test_mul', 'entity_test_mul_property_data']); + ->willReturn(['entity_test_mul', 'entity_test_mul_property_data', 'entity_test_mul__string']); $table_mapping->expects($this->any()) ->method('getColumnNames') ->willReturnMap([ @@ -544,12 +586,14 @@ class EntityViewsDataTest extends UnitTestCase { ['description', ['value' => 'description__value', 'format' => 'description__format']], ['homepage', ['value' => 'homepage']], ['user_id', ['target_id' => 'user_id']], + ['string', ['value' => 'value']], ]); $table_mapping->expects($this->any()) ->method('getFieldNames') ->willReturnMap([ ['entity_test_mul', ['uuid']], ['entity_test_mul_property_data', ['id', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']], + ['entity_test_mul__string', ['string']], ]); $table_mapping->expects($this->any()) @@ -560,6 +604,18 @@ class EntityViewsDataTest extends UnitTestCase { } return 'entity_test_mul_property_data'; }); + $table_mapping->expects($this->any()) + ->method('requiresDedicatedTableStorage') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + return $base_field->getName() === 'string'; + }); + $table_mapping->expects($this->any()) + ->method('getDedicatedDataTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test_mul__string'; + } + }); $this->entityStorage->expects($this->once()) ->method('getTableMapping') @@ -619,6 +675,18 @@ class EntityViewsDataTest extends UnitTestCase { $relationship = $data['entity_test_mul_property_data']['user_id']['relationship']; $this->assertEquals('users_field_data', $relationship['base']); $this->assertEquals('uid', $relationship['base field']); + + $this->assertStringField($data['entity_test_mul__string']['string']); + $this->assertField($data['entity_test_mul__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test_mul__string']['table']['join']['entity_test_mul']); } /** @@ -650,10 +718,12 @@ class EntityViewsDataTest extends UnitTestCase { $this->viewsData->setEntityType($entity_type); // Setup the table mapping. - $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) + ->disableOriginalConstructor() + ->getMock(); $table_mapping->expects($this->any()) ->method('getTableNames') - ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision']); + ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision', 'entity_test_mulrev__string', 'entity_test_mulrev_revision__string']); $table_mapping->expects($this->any()) ->method('getColumnNames') ->willReturnMap([ @@ -666,6 +736,7 @@ class EntityViewsDataTest extends UnitTestCase { ['homepage', ['value' => 'homepage']], ['user_id', ['target_id' => 'user_id']], ['revision_id', ['value' => 'id']], + ['string', ['value' => 'value']], ]); $table_mapping->expects($this->any()) ->method('getFieldNames') @@ -674,7 +745,29 @@ class EntityViewsDataTest extends UnitTestCase { ['entity_test_mulrev_revision', ['id', 'revision_id', 'langcode']], ['entity_test_mulrev_property_data', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']], ['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']], + ['entity_test_mulrev__string', ['string']], + ['entity_test_mulrev_revision__string', ['string']], ]); + $table_mapping->expects($this->any()) + ->method('requiresDedicatedTableStorage') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + return $base_field->getName() === 'string'; + }); + $table_mapping->expects($this->any()) + ->method('getDedicatedDataTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test_mulrev__string'; + } + }); + + $table_mapping->expects($this->any()) + ->method('getDedicatedRevisionTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test_mulrev_revision__string'; + } + }); $table_mapping->expects($this->any()) ->method('getFieldTableName') @@ -767,6 +860,30 @@ class EntityViewsDataTest extends UnitTestCase { $relationship = $data['entity_test_mulrev_property_revision']['user_id']['relationship']; $this->assertEquals('users_field_data', $relationship['base']); $this->assertEquals('uid', $relationship['base field']); + + $this->assertStringField($data['entity_test_mulrev__string']['string']); + $this->assertField($data['entity_test_mulrev__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test_mulrev__string']['table']['join']['entity_test_mulrev_property_data']); + + $this->assertStringField($data['entity_test_mulrev_revision__string']['string']); + $this->assertField($data['entity_test_mulrev_revision__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'revision_id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test_mulrev_revision__string']['table']['join']['entity_test_mulrev_property_revision']); } /** diff --git a/core/modules/views/views.install b/core/modules/views/views.install index ba2b49df7a9..1068c9e7e94 100644 --- a/core/modules/views/views.install +++ b/core/modules/views/views.install @@ -360,6 +360,13 @@ function views_update_8005() { // Empty update function to rebuild the views data. } +/** + * Clear caches due to updated entity views data. + */ +function views_update_8100() { + // Empty update to cause a cache flush so that views data is rebuilt. +} + /** * @} End of "addtogroup updates-8.1.0". */