diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index 7fd9977..49c6cc5 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -12,6 +12,8 @@ use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Entity\Schema\ContentEntitySchemaHandler; use Drupal\Core\Entity\Schema\ContentEntitySchemaHandlerInterface; +use Drupal\Core\Entity\Sql\DefaultTableMapping; +use Drupal\Core\Entity\Sql\SqlStorageInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Language\Language; use Drupal\field\FieldInfo; @@ -83,7 +85,7 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S /** * A mapping of schema fields that will be stored per entity table. * - * @var array + * @var \Drupal\Core\Entity\Sql\DefaultTableMapping */ protected $tableMapping; @@ -329,7 +331,7 @@ protected function schemaHandler() { */ public function getTableMapping() { if (!isset($this->tableMapping)) { - $table_mapping = array(); + $this->tableMapping = new DefaultTableMapping($this->storageDefinitions); $key_fields = array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey)); $all_fields = array_keys($this->storageDefinitions); @@ -350,7 +352,7 @@ public function getTableMapping() { switch ($this->getTableLayout()) { // The base layout stores all the base field values in the base table. case static::LAYOUT_BASE: - $table_mapping[$this->baseTable] = $this->getColumnMapping($all_fields); + $this->tableMapping->addFieldColumns($this->baseTable, $all_fields); break; // The revisionable layout stores all the base field values in the base @@ -358,10 +360,10 @@ public function getTableMapping() { // denormalized in the base table but also stored in the revision table // together with the entity ID and the revision ID as identifiers. case static::LAYOUT_REVISION: - $table_mapping[$this->baseTable] = $this->getColumnMapping(array_diff($all_fields, $revision_metadata_fields)); + $this->tableMapping->addFieldColumns($this->baseTable, array_diff($all_fields, $revision_metadata_fields)); $revision_key_fields = array($this->idKey, $this->revisionKey); - $table_mapping[$this->revisionTable] = $this->getColumnMapping(array_merge($revision_key_fields, $revisionable_fields)); + $this->tableMapping->addFieldColumns($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); break; // Multilingual layouts store key field values in the base table. The @@ -371,14 +373,13 @@ public function getTableMapping() { // performant queries. This means that only the UUID is not stored on // the data table. case static::LAYOUT_MULTILINGUAL: - $table_mapping[$this->baseTable] = $this->getColumnMapping($key_fields); - - $table_mapping[$this->dataTable] = $this->getColumnMapping(array_diff($all_fields, array($this->uuidKey))); - // Add the denormalized 'default_langcode' field to the mapping. Its - // value is identical to the query expression - // "base_table.langcode = data_table.langcode". As it does not - // correspond to a field definition we add it with an empty key. - $table_mapping[$this->dataTable][''] = array('default_langcode'); + $this->tableMapping + ->addFieldColumns($this->baseTable, $key_fields) + ->addFieldColumns($this->dataTable, array_diff($all_fields, array($this->uuidKey))) + // Add the denormalized 'default_langcode' field to the mapping. Its + // value is identical to the query expression + // "base_table.langcode = data_table.langcode" + ->addExtraColumns($this->dataTable, array('default_langcode')); break; // The revisionable multilingual layout stores key field values in the @@ -386,66 +387,39 @@ public function getTableMapping() { // table along with revision metadata. The revision data table holds // data field values for all the reivisionable fields and the case static::LAYOUT_MULTILINGUAL_REVISION: - $table_mapping[$this->baseTable] = $this->getColumnMapping(array_diff($key_fields, array($this->langcodeKey))); + $this->tableMapping->addFieldColumns($this->baseTable, array_diff($key_fields, array($this->langcodeKey))); // Like in the multilingual, non-revisionable case the UUID is not // in the data table. Additionally, do not store revision metadata // fields in the data table. $data_fields = array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields); - $table_mapping[$this->dataTable] = $this->getColumnMapping($data_fields); - // Add the denormalized 'default_langcode' field to the mapping. Its - // value is identical to the query expression - // "base_langcode = data_table.langcode" where "base_langcode" is - // the language code of the default revision. As it does not - // correspond to a field definition we add it with an empty key. - $table_mapping[$this->dataTable][''] = array('default_langcode'); + $this->tableMapping + ->addFieldColumns($this->dataTable, $data_fields) + // Add the denormalized 'default_langcode' field to the mapping. Its + // value is identical to the query expression + // "base_langcode = data_table.langcode" where "base_langcode" is + // the language code of the default revision. + ->addExtraColumns($this->dataTable, array('default_langcode')); $revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields); - $table_mapping[$this->revisionTable] = $this->getColumnMapping($revision_base_fields); + $this->tableMapping->addFieldColumns($this->revisionTable, $revision_base_fields); $revision_data_key_fields = array($this->idKey, $this->revisionKey, $this->langcodeKey); $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields); - $table_mapping[$this->revisionDataTable] = $this->getColumnMapping(array_merge($revision_data_key_fields, $revision_data_fields)); - // Add the denormalized 'default_langcode' field to the mapping. Its - // value is identical to the query expression - // "revision_table.langcode = data_table.langcode". As it does not - // correspond to a field definition we add it with an empty key. - $table_mapping[$this->revisionDataTable][''] = array('default_langcode'); + $this->tableMapping + ->addFieldColumns($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields)) + // Add the denormalized 'default_langcode' field to the mapping. Its + // value is identical to the query expression + // "revision_table.langcode = data_table.langcode". + ->addExtraColumns($this->revisionDataTable, array('default_langcode')); break; } - - $this->tableMapping = $table_mapping; } return $this->tableMapping; } /** - * Returns a mapping between field and column names. - * - * @param array $field_names - * An array of names of fields to map. - * - * @return array - * An associative array of arrays of column names keyed by field name. - */ - protected function getColumnMapping($field_names) { - $mapping = array(); - foreach ($field_names as $field_name) { - $column_names = array_keys($this->storageDefinitions[$field_name]->getColumns()); - if (count($column_names) == 1) { - $mapping[$field_name] = array(reset($column_names) => $field_name); - } - else { - foreach ($column_names as $column_name) { - $mapping[$field_name][$column_name] = $field_name . '__' . $column_name; - } - } - } - return $mapping; - } - - /** * {@inheritdoc} */ protected function doLoadMultiple(array $ids = NULL) { @@ -547,10 +521,10 @@ protected function attachPropertyData(array &$entities) { $table_mapping = $this->getTableMapping(); $translations = array(); if ($this->revisionDataTable) { - $data_column_mapping = array_diff_key($table_mapping[$this->revisionDataTable], $table_mapping[$this->baseTable]); + $data_column_mapping = array_diff_key($table_mapping->getFieldColumns($this->revisionDataTable), $table_mapping->getFieldColumns($this->baseTable)); } else { - $data_column_mapping = $table_mapping[$this->dataTable]; + $data_column_mapping = $table_mapping->getFieldColumns($this->dataTable); } foreach ($data as $values) { @@ -674,11 +648,11 @@ protected function buildQuery($ids, $revision_id = FALSE) { // Add fields from the {entity} table. $table_mapping = $this->getTableMapping(); - $entity_fields = call_user_func_array('array_merge', array_map('array_values', $table_mapping[$this->baseTable])); + $entity_fields = $table_mapping->getAllColumns($this->baseTable); if ($this->revisionTable) { // Add all fields from the {entity_revision} table. - $entity_revision_fields = call_user_func_array('array_merge', array_map('array_values', $table_mapping[$this->revisionTable])); + $entity_revision_fields = $table_mapping->getAllColumns($this->revisionTable); $entity_revision_fields = array_combine($entity_revision_fields, $entity_revision_fields); // The ID field is provided by entity, so remove it. unset($entity_revision_fields[$this->idKey]); @@ -941,21 +915,15 @@ protected function mapToStorageRecord(ContentEntityInterface $entity, $table_nam } $record = new \stdClass(); - $is_new = $entity->isNew(); - $table_mapping = $this->getTableMapping(); - - foreach ($table_mapping[$table_name] as $name => $storage_columns) { - if (!empty($this->storageDefinitions[$name])) { - $definition = $this->storageDefinitions[$name]; - foreach ($definition->getColumns() as $column => $column_info) { - $value = isset($entity->$name->$column) ? $entity->$name->$column : NULL; - if (!empty($column_info['serialize'])) { + foreach ($this->getTableMapping()->getFieldColumns($table_name) as $field_name => $columns) { + if (!empty($this->storageDefinitions[$field_name])) { + $definition = $this->storageDefinitions[$field_name]; + foreach ($columns as $column_name => $schema_name) { + $value = isset($entity->$field_name->$column_name) ? $entity->$field_name->$column_name : NULL; + if (!empty($definition->getSchema()[$column_name]['serialize'])) { $value = serialize($value); } - list(, $storage_column) = each($storage_columns); - if (!empty($storage_column)) { - $record->$storage_column = drupal_schema_get_field_value($definition->getSchema()['columns'][$column], $value); - } + $record->$schema_name = drupal_schema_get_field_value($definition->getSchema()['columns'][$column_name], $value); } } } diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php index 2a58af3..122d0f4 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php @@ -12,7 +12,7 @@ use Drupal\Core\Entity\ContentEntityDatabaseStorage; use Drupal\Core\Entity\Plugin\DataType\EntityReference; use Drupal\Core\Entity\Query\QueryException; -use Drupal\Core\Entity\SqlStorageInterface; +use Drupal\Core\Entity\Sql\SqlStorageInterface; use Drupal\field\Entity\FieldConfig; use Drupal\field\Field as FieldInfo; use Drupal\Core\Entity\EntityType; @@ -295,8 +295,7 @@ protected function getTableMapping($table, $entity_type_id) { // to the Entity Field API. See https://drupal.org/node/1842858. $schema = drupal_get_schema($table); if (!$schema && $storage instanceof SqlStorageInterface) { - $mapping = $storage->getTableMapping(); - $mapping = isset($mapping[$table]) ? call_user_func_array('array_merge', array_map('array_values', $mapping[$table])) : FALSE; + $mapping = $storage->getTableMapping()->getAllColumns($table); } else { $mapping = array_keys($schema['fields']); diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php index 30afff0..42c5c92 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -79,21 +79,17 @@ public function getSchema() { $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable(); } - // Add the schema from field definitions. - foreach ($this->storage->getTableMapping() as $table => $field_names) { - foreach ($field_names as $field_name => $column_names) { - // If there are table fields which do not correspond to entity field - // definitions, those are stored with the '' key. - if ($field_name !== '') { - $this->addFieldSchema($schema[$table], $field_name, $column_names); - } - else { - foreach ($column_names as $column_name) { - // Support hardcoded non-entity-field table fields. - if ($column_name == 'default_langcode') { - $this->addDefaultLangcodeSchema($schema[$table]); - } - } + $table_mapping = $this->storage->getTableMapping(); + foreach ($table_mapping->getTableNames() as $table_name) { + // Add the schema from field definitions. + foreach ($table_mapping->getFieldColumns($table_name) as $field_name => $column_names) { + $this->addFieldSchema($schema[$table_name], $field_name, $column_names); + } + + // Add the schema for extra fields. + foreach ($table_mapping->getExtraColumns($table_name) as $column_name) { + if ($column_name == 'default_langcode') { + $this->addDefaultLangcodeSchema($schema[$table_name]); } } } diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php new file mode 100644 index 0000000..dc83de7 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php @@ -0,0 +1,124 @@ +storageDefinitions = $storage_definitions; + } + + /** + * {@inheritdoc} + */ + public function getTableNames() { + return array_unique(array_merge(array_keys($this->fieldColumns), array_keys($this->extraColumns))); + } + + /** + * {@inheritdoc} + */ + public function getAllColumns($table_name) { + $columns = array_map('array_merge', array_map('array_values', $this->fieldColumns[$table_name])); + return array_merge($columns, $this->extraColumns[$table_name]); + } + + /** + * {@inheritdoc} + */ + public function getFieldColumns($table_name) { + return $this->fieldColumns[$table_name]; + } + + /** + * Adds field columns for a table to the table mapping. + * + * @param string $table_name + * The name of the table to add the field column for. + * @param string[] $field_names + * A list of field names to add the columns for. + * + * @return $this + */ + public function addFieldColumns($table_name, array $field_names) { + foreach ($field_names as $field_name) { + $column_names = array_keys($this->storageDefinitions[$field_name]->getColumns()); + if (count($column_names) == 1) { + $this->fieldColumns[$table_name][$field_name] = array(reset($column_names) => $field_name); + } + else { + foreach ($column_names as $column_name) { + $this->fieldColumns[$table_name][$field_name][$column_name] = $field_name . '__' . $column_name; + } + } + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getExtraColumns($table_name) { + return $this->extraColumns[$table_name]; + } + + /** + * Adds a extra columns for a table to the table mapping. + * + * @param string $table_name + * The name of table to add the extra columns for. + * @param string[] $column_names + * The list of column names. + * + * @return $this + */ + public function addExtraColumns($table_name, array $column_names) { + $this->extraColumns[$table_name] = $column_names; + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlStorageInterface.php b/core/lib/Drupal/Core/Entity/Sql/SqlStorageInterface.php new file mode 100644 index 0000000..d16b3c8 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Sql/SqlStorageInterface.php @@ -0,0 +1,23 @@ + array( + * 'value' => 'title', + * ), + * ), + * @endcode + * + * For a TextItem field (a multi-column field) named 'description' + * DefaultTableMapping::getFieldColumns() would return: + * @code + * array( + * 'description' => array( + * 'value' => 'description__value', + * 'format' => 'description_format', + * ), + * ), + * @endcode + * + * The values of the inner array ('title', 'description__value', and + * 'description_format' in the example above) depend on the table mapping + * implementation. + * + * @param string $table_name + * The name of the table to return the columns for. + * + * @return array[] + * An array where the keys are the names of the entity fields and the values + * are in turn arrays whose keys are the names of the columns as specified + * in the field item's schema() method and the values are the respective + * database column names for the respective entity fields. + */ + public function getFieldColumns($table_name); + + /** + * Returns a list of database columns which store denormalized data. + * + * These database columns do not belong to any entity fields. Any normalized + * data that is stored should be associated with an entity field. + * + * @param string $table_name + * The name of the table to return the columns for. + * + * @return string[] + * An array of column names for the given table. + */ + public function getExtraColumns($table_name); + +} diff --git a/core/lib/Drupal/Core/Entity/SqlStorageInterface.php b/core/lib/Drupal/Core/Entity/SqlStorageInterface.php deleted file mode 100644 index cd11111..0000000 --- a/core/lib/Drupal/Core/Entity/SqlStorageInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -