diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index ac60b72..2249180 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -58,6 +58,13 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S protected $revisionKey = FALSE; /** + * The entity langcode key + * + * @var string|bool + */ + protected $langcodeKey = FALSE; + + /** * The base table of the entity. * * @var string @@ -251,8 +258,8 @@ public function getTableMapping() { // Make sure the key fields come first in the list of fields. $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields)); - - // Nodes have all three of these fields, while custom blocks only have log + // Nodes have all three of these fields, while custom blocks only have + // log. // @todo Provide automatic definitions for revision metadata fields in // https://drupal.org/node/2248983. // @todo Rename 'log' to 'revision_log' in @@ -263,12 +270,13 @@ public function getTableMapping() { // entity query and we have a views data controller. See: // - https://drupal.org/node/2068325 // - https://drupal.org/node/1740492 + $revisionable = $this->entityType->hasKey('revision'); $translatable = $this->entityType->getDataTable() && $this->entityType->isTranslatable(); - if (!$this->entityType->hasKey('revision') && !$translatable) { + if (!$revisionable && !$translatable) { // The base layout stores all the base field values in the base table. $this->tableMapping->setFieldNames($this->baseTable, $all_fields); } - elseif ($this->entityType->hasKey('revision') && !$translatable) { + elseif ($revisionable && !$translatable) { // The revisionable layout stores all the base field values in the base // table, except for revision metadata fields. Revisionable fields // denormalized in the base table but also stored in the revision table @@ -278,7 +286,7 @@ public function getTableMapping() { $revision_key_fields = array($this->idKey, $this->revisionKey); $this->tableMapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); } - elseif (!$this->entityType->hasKey('revision') && $translatable) { + elseif (!$revisionable && $translatable) { // Multilingual layouts store key field values in the base table. The // other base field values are stored in the data table, no matter // whether they are translatable or not. The data table holds also a @@ -293,7 +301,7 @@ public function getTableMapping() { // "base_table.langcode = data_table.langcode" ->setExtraColumns($this->dataTable, array('default_langcode')); } - elseif ($this->entityType->hasKey('revision') && $translatable) { + elseif ($revisionable && $translatable) { // The revisionable multilingual layout stores key field values in the // base table, except for language, which is stored in the revision // table along with revision metadata. The revision data table holds @@ -450,7 +458,7 @@ protected function attachPropertyData(array &$entities) { foreach ($data_fields as $field_name) { - $columns = $table_mapping->getColumnMapping($field_name); + $columns = $table_mapping->getColumnNames($field_name); // Do not key single-column fields by property name. if (count($columns) == 1) { $entities[$id][$field_name][$langcode] = $values[reset($columns)]; @@ -831,7 +839,7 @@ protected function mapToStorageRecord(ContentEntityInterface $entity, $table_nam $record = new \stdClass(); $table_mapping = $this->getTableMapping(); foreach ($table_mapping->getFieldNames($table_name) as $field_name) { - $columns = $table_mapping->getColumnMapping($field_name); + $columns = $table_mapping->getColumnNames($field_name); if (!empty($this->storageDefinitions[$field_name])) { $definition = $this->storageDefinitions[$field_name]; foreach ($columns as $column_name => $schema_name) { diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 203dde3..b40f73a 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -23,13 +23,6 @@ protected $bundleKey = FALSE; /** - * The entity langcode key - * - * @var string|bool - */ - protected $langcodeKey = FALSE; - - /** * Constructs a ContentEntityStorageBase object. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type @@ -38,7 +31,6 @@ public function __construct(EntityTypeInterface $entity_type) { parent::__construct($entity_type); - $this->uuidKey = $this->entityType->getKey('uuid'); $this->bundleKey = $this->entityType->getKey('bundle'); } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index 5d24441..f10eba9 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -76,6 +76,7 @@ public function __construct(EntityTypeInterface $entity_type) { $this->entityTypeId = $entity_type->id(); $this->entityType = $entity_type; $this->idKey = $this->entityType->getKey('id'); + $this->uuidKey = $this->entityType->getKey('uuid'); $this->entityClass = $this->entityType->getClass(); } diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php index 46b4828..d6c348b 100644 --- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php +++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php @@ -44,7 +44,7 @@ class DefaultTableMapping implements TableMappingInterface { * A mapping of column names per field name. * * This corresponds to the return value of - * TableMappingInterface::getColumnMapping() except that this variable is + * TableMappingInterface::getColumnNames() except that this variable is * additionally keyed by field name. * * This data is derived from static::$storageDefinitions, but is stored diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php index dbeb7fa..7e3c6e6 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php @@ -238,10 +238,10 @@ public function providerTestGetRevisionDataTable() { /** * Tests ContentEntityDatabaseStorage::getSchema(). * - * @covers ::__construct - * @covers ::getSchema - * @covers ::schemaHandler - * @covers ::getTableMapping + * @covers ::__construct() + * @covers ::getSchema() + * @covers ::schemaHandler() + * @covers ::getTableMapping() */ public function testGetSchema() { $columns = array( @@ -306,6 +306,257 @@ public function testGetSchema() { } /** + * Tests getTableMapping() with an empty entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + */ + public function testGetTableMappingEmpty() { + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + $this->assertSame(array('entity_test'), $mapping->getTableNames()); + $this->assertSame(array(), $mapping->getFieldNames('entity_test')); + $this->assertSame(array(), $mapping->getExtraColumns('entity_test')); + } + + /** + * Tests getTableMapping() with a simple entity type. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingSimple(array $entity_keys) { + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $this->assertEquals(array('entity_test'), $mapping->getTableNames()); + + $expected = array_values(array_filter($entity_keys)); + $this->assertEquals($expected, $mapping->getFieldNames('entity_test')); + + $this->assertEquals(array(), $mapping->getExtraColumns('entity_test')); + } + + /** + * Tests getTableMapping() with a simple entity type with some base fields. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingSimpleWithFields(array $entity_keys) { + $base_field_names = array('title', 'description', 'owner'); + $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); + + $definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $this->fieldDefinitions = array_fill_keys($field_names, $definition); + + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + $this->assertEquals(array('entity_test'), $mapping->getTableNames()); + $this->assertEquals($field_names, $mapping->getFieldNames('entity_test')); + $this->assertEquals(array(), $mapping->getExtraColumns('entity_test')); + } + + /** + * Provides test data for testGetTableMappingSimple(). + * + * @return array[] + * A nested array, where each inner array has a single value being a map of + * entity keys to use for the mocked entity type. + */ + public function providerTestGetTableMappingSimple() { + return array( + array(array( + 'id' => 'test_id', + 'bundle' => NULL, + 'uuid' => NULL, + )), + array(array( + 'id' => 'test_id', + 'bundle' => 'test_bundle', + 'uuid' => NULL, + )), + array(array( + 'id' => 'test_id', + 'bundle' => NULL, + 'uuid' => 'test_uuid', + )), + array(array( + 'id' => 'test_id', + 'bundle' => 'test_bundle', + 'uuid' => 'test_uuid', + )), + ); + } + + /** + * Tests getTableMapping() with a revisionable, non-translatable entity type. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingRevisionable(array $entity_keys) { + // This allows to re-use the data provider. + $entity_keys = array( + 'id' => $entity_keys['id'], + 'revision' => 'test_revision', + 'bundle' => $entity_keys['bundle'], + 'uuid' => $entity_keys['uuid'], + ); + + $this->entityType->expects($this->exactly(2)) + ->method('hasKey') + ->with('revision') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + array('revision', $entity_keys['revision']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $expected = array('entity_test', 'entity_test_revision'); + $this->assertEquals($expected, $mapping->getTableNames()); + + $expected = array_values(array_filter($entity_keys)); + $this->assertEquals($expected, $mapping->getFieldNames('entity_test')); + $expected = array($entity_keys['id'], $entity_keys['revision']); + $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision')); + + $this->assertEquals(array(), $mapping->getExtraColumns('entity_test')); + $this->assertEquals(array(), $mapping->getExtraColumns('entity_test_revision')); + } + + /** + * Tests getTableMapping() with a revisionable entity type with fields. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingRevisionableWithFields(array $entity_keys) { + // This allows to re-use the data provider. + $entity_keys = array( + 'id' => $entity_keys['id'], + 'revision' => 'test_revision', + 'bundle' => $entity_keys['bundle'], + 'uuid' => $entity_keys['uuid'], + ); + + // PHPUnit does not allow for multiple data providers. + $test_cases = array( + array(), + array('revision_timestamp'), + array('revision_uid'), + array('log'), + array('revision_timestamp', 'revision_uid'), + array('revision_timestamp', 'log'), + array('revision_uid', 'log'), + array('revision_timestamp', 'revision_uid', 'log'), + ); + foreach ($test_cases as $revision_metadata_field_names) { + $this->setUp(); + + $base_field_names = array('title'); + $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); + + $definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $this->fieldDefinitions = array_fill_keys($field_names, $definition); + + $revisionable_field_names = array('description', 'owner'); + $definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + // isRevisionable() is only called once, but we re-use the same definition + // for all revisionable fields. + $definition->expects($this->any()) + ->method('isRevisionable') + ->will($this->returnValue(TRUE)); + $field_names = array_merge( + $field_names, + $revisionable_field_names + ); + $this->fieldDefinitions += array_fill_keys( + array_merge($revisionable_field_names, $revision_metadata_field_names), + $definition + ); + + $this->entityType->expects($this->exactly(2)) + ->method('hasKey') + ->with('revision') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + array('revision', $entity_keys['revision']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $expected = array('entity_test', 'entity_test_revision'); + $this->assertEquals($expected, $mapping->getTableNames()); + + $this->assertEquals($field_names, $mapping->getFieldNames('entity_test')); + $expected = array_merge( + array($entity_keys['id'], $entity_keys['revision']), + $revisionable_field_names, + $revision_metadata_field_names + ); + $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision')); + + $this->assertEquals(array(), $mapping->getExtraColumns('entity_test')); + $this->assertEquals(array(), $mapping->getExtraColumns('entity_test_revision')); + } + } + + /** * Tests field SQL schema generation for an entity with a string identifier. * * @covers ::_fieldSqlSchema() diff --git a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php index f6e28bd..5b67329 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php @@ -97,19 +97,19 @@ public function setUp() { * @param bool $uuid_key * Whether or not the tested entity type should have a UUID key. * - * @covers ::__construct - * @covers ::getSchema - * @covers ::getTables - * @covers ::initializeBaseTable - * @covers ::getEntityIndexName - * @covers ::addFieldSchema - * @covers ::getFieldIndexes - * @covers ::getFieldUniqueKeys - * @covers ::getFieldForeignKeys - * @covers ::getFieldSchemaData - * @covers ::addDefaultLangcodeSchema - * @covers ::processBaseTable - * @covers ::processIdentifierSchema + * @covers ::__construct() + * @covers ::getSchema() + * @covers ::getTables() + * @covers ::initializeBaseTable() + * @covers ::getEntityIndexName() + * @covers ::addFieldSchema() + * @covers ::getFieldIndexes() + * @covers ::getFieldUniqueKeys() + * @covers ::getFieldForeignKeys() + * @covers ::getFieldSchemaData() + * @covers ::addDefaultLangcodeSchema() + * @covers ::processBaseTable() + * @covers ::processIdentifierSchema() * * @dataProvider providerTestGetSchemaLayoutBase */ @@ -366,14 +366,14 @@ public function providerTestGetSchemaLayoutBase() { /** * Tests the schema for revisionable, non-translatable entities. * - * @covers ::__construct - * @covers ::getSchema - * @covers ::getTables - * @covers ::initializeBaseTable - * @covers ::initializeRevisionTable - * @covers ::getEntityIndexName - * @covers ::processRevisionTable - * @covers ::processIdentifierSchema + * @covers ::__construct() + * @covers ::getSchema() + * @covers ::getTables() + * @covers ::initializeBaseTable() + * @covers ::initializeRevisionTable() + * @covers ::getEntityIndexName() + * @covers ::processRevisionTable() + * @covers ::processIdentifierSchema() */ public function testGetSchemaRevisionable() { $this->entityType = new ContentEntityType(array( @@ -468,12 +468,12 @@ public function testGetSchemaRevisionable() { /** * Tests the schema for non-revisionable, translatable entities. * - * @covers ::__construct - * @covers ::getSchema - * @covers ::getTables - * @covers ::initializeDataTable - * @covers ::getEntityIndexName - * @covers ::processDataTable + * @covers ::__construct() + * @covers ::getSchema() + * @covers ::getTables() + * @covers ::initializeDataTable() + * @covers ::getEntityIndexName() + * @covers ::processDataTable() */ public function testGetSchemaTranslatable() { $this->entityType = new ContentEntityType(array( @@ -557,13 +557,13 @@ public function testGetSchemaTranslatable() { /** * Tests the schema for revisionable, translatable entities. * - * @covers ::__construct - * @covers ::getSchema - * @covers ::getTables - * @covers ::initializeDataTable - * @covers ::getEntityIndexName - * @covers ::initializeRevisionDataTable - * @covers ::processRevisionDataTable + * @covers ::__construct() + * @covers ::getSchema() + * @covers ::getTables() + * @covers ::initializeDataTable() + * @covers ::getEntityIndexName() + * @covers ::initializeRevisionDataTable() + * @covers ::processRevisionDataTable() */ public function testGetSchemaRevisionableTranslatable() { $this->entityType = new ContentEntityType(array( @@ -784,4 +784,3 @@ public function setUpStorageDefinition($field_name, array $schema) { } } - diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php index f6ac667..501315d 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php @@ -32,10 +32,10 @@ public static function getInfo() { /** * Tests DefaultTableMapping::getTableNames(). * - * @covers ::getTableNames + * @covers ::getTableNames() */ public function testGetTableNames() { - // The storage definitions are only used in getColumnMapping() so we do not + // The storage definitions are only used in getColumnNames() so we do not // need to provide any here. $table_mapping = new DefaultTableMapping([]); $this->assertSame([], $table_mapping->getTableNames()); @@ -172,7 +172,7 @@ public function testGetAllColumns() { * @covers ::setFieldNames() */ public function testGetFieldNames() { - // The storage definitions are only used in getColumnMapping() so we do not + // The storage definitions are only used in getColumnNames() so we do not // need to provide any here. $table_mapping = new DefaultTableMapping([]); @@ -196,10 +196,10 @@ public function testGetFieldNames() { } /** - * Tests DefaultTableMapping::getColumnMapping(). + * Tests DefaultTableMapping::getColumnNames(). * * @covers ::__construct() - * @covers ::getColumnNames + * @covers ::getColumnNames() */ public function testGetColumnNames() { $definitions['test'] = $this->setUpDefinition([]); @@ -225,7 +225,7 @@ public function testGetColumnNames() { * @covers ::setExtraColumns() */ public function testGetExtraColumns() { - // The storage definitions are only used in getColumnMapping() so we do not + // The storage definitions are only used in getColumnNames() so we do not // need to provide any here. $table_mapping = new DefaultTableMapping([]);