diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index f8bb2af..acecba0 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -439,7 +439,7 @@ function install_begin_request(&$install_state) { $config = \Drupal::service('config.storage')->listAll(); if (!empty($config)) { $task = NULL; - throw new AlreadyInstalledException($container->get('string_translation')); + //throw new AlreadyInstalledException($container->get('string_translation')); } } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index 3720778..c701de3 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -30,7 +30,7 @@ * This class can be used as-is by most simple entity types. Entity types * requiring special handling can extend the class. */ -class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements ContentEntityDatabaseStorageInterface, ContentEntitySchemaHandlerInterface { +class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements ContentEntityDefaultDatabaseStorageInterface, ContentEntitySchemaHandlerInterface { /** * The base field definitions for this entity type. @@ -42,9 +42,14 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements C /** * The table layout associated to the entity type. * + * @see static::LAYOUT_BASE + * @see static::LAYOUT_REVISION + * @see static::LAYOUT_MULTILINGUAL + * @see static::LAYOUT_MULTILINGUAL_REVISION + * * @var int */ - protected $layoutType; + protected $tableLayout; /** * A mapping of schema fields that will be stored per entity table. @@ -63,7 +68,7 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements C protected $revisionKey = FALSE; /** - * The base table of the entity, if the entity has storage. + * The base table of the entity. * * @var string */ @@ -150,7 +155,8 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa $this->fieldInfo = $field_info; $this->entityManager = $entity_manager; $this->storageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id(), FALSE); - // @todo Remove this when multiple-value base fields are supported. + // @todo Remove this when multiple-value base fields are supported in + // https://drupal.org/node/2248977. $this->storageDefinitions = array_filter($this->storageDefinitions, function (FieldStorageDefinitionInterface $definition) { return !$definition->isMultiple(); }); @@ -174,7 +180,7 @@ protected function initTableLayout() { // Retrieve the current table layout type based on the entity type // definition. - $layout_type = $this->getLayoutType(); + $layout_type = $this->getTableLayout(); if ($layout_type & static::LAYOUT_REVISION) { $this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id'; @@ -195,11 +201,11 @@ protected function initTableLayout() { /** * {@inheritdoc} */ - public function getLayoutType() { - if (!isset($this->layoutType)) { - $this->layoutType = static::LAYOUT_BASE; + public function getTableLayout() { + if (!isset($this->tableLayout)) { + $this->tableLayout = static::LAYOUT_BASE; if ($this->entityType->hasKey('revision')) { - $this->layoutType |= static::LAYOUT_REVISION; + $this->tableLayout |= static::LAYOUT_REVISION; } // @todo Remove the data table check once all entity types are using // entity query and we have a views data controller. See: @@ -207,10 +213,10 @@ public function getLayoutType() { // - https://drupal.org/node/1740492 $data_table = $this->entityType->getDataTable(); if ($data_table && $this->entityType->isTranslatable()) { - $this->layoutType |= static::LAYOUT_MULTILINGUAL; + $this->tableLayout |= static::LAYOUT_MULTILINGUAL; } } - return $this->layoutType; + return $this->tableLayout; } /** @@ -283,32 +289,37 @@ public function getTableMapping() { $table_mapping = array(); $key_fields = array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey)); - - // Storable fields are single-value base fields that are not defined as - // computed and that do not specify a custom storage. - // @todo Add support for multiple-value base fields. - $storable_definitions = array_filter($this->fieldDefinitions, function (FieldDefinitionInterface $field_definition) { - return !$field_definition->isComputed() && !$field_definition->hasCustomStorage() && !$field_definition->isMultiple(); - }); - $storable_fields = array_keys($storable_definitions); - - // @todo Provide automatic definitions for revision metadata fields. - // Rename 'log' to 'revision_log'. - $revision_metadata_fields = array_intersect(array('revision_timestamp', 'revision_uid', 'log'), $storable_fields); - $revisionable_filter_callback = function (FieldDefinitionInterface $definition) { return $definition->isRevisionable(); }; - - switch ($this->getLayoutType()) { + $all_fields = array_keys($this->storageDefinitions); + $revisionable_fields = array_keys(array_filter($this->storageDefinitions, function (FieldStorageDefinitionInterface $definition) { + return $definition->isRevisionable(); + })); + // 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 + // @todo Provide automatic definitions for revision metadata fields in + // https://drupal.org/node/2248983. + // @todo Rename 'log' to 'revision_log' in + // https://drupal.org/node/2248991. + $revision_metadata_fields = array_intersect(array('revision_timestamp', 'revision_uid', 'log'), $all_fields); + $revisionable_filter_callback = function (FieldDefinitionInterface $definition) { + return $definition->isRevisionable(); + }; + + 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(array_merge($key_fields, array_diff($storable_fields, $key_fields))); + $table_mapping[$this->baseTable] = $this->getColumnMapping($all_fields); break; - // The base layout stores all the base field values in the base table. - // Revisionable fields are also stored in the revision table. + // The base 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. case static::LAYOUT_REVISION: - $table_mapping[$this->baseTable] = $this->getColumnMapping(array_merge($key_fields, array_diff($storable_fields, $key_fields, $revision_metadata_fields))); + $table_mapping[$this->baseTable] = $this->getColumnMapping(array_diff($all_fields, $revision_metadata_fields)); + $revision_key_fields = array($this->idKey, $this->revisionKey); - $revisionable_fields = array_keys(array_filter($storable_definitions, $revisionable_filter_callback)); $table_mapping[$this->revisionTable] = $this->getColumnMapping(array_merge($revision_key_fields, $revisionable_fields)); break; @@ -316,12 +327,12 @@ public function getTableMapping() { // other base field values are stored in the data table, no matter // whether they are translatable or not. The data table holds also a // denormalized copy of the bundle field value to allow for more - // performant queries. + // 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); - $data_key_fields = array_diff($key_fields, array($this->uuidKey)); - $data_fields = array_diff($storable_fields, $key_fields); - $table_mapping[$this->dataTable] = $this->getColumnMapping(array_merge($data_key_fields, $data_fields)); + + $table_mapping[$this->dataTable] = $this->getColumnMapping(array_diff($all_fields, array($this->uuidKey))); // Add the denormalized 'default_langcode' field to the mapping. As it // does not correspond to a field definition we add it with an empty // key. @@ -334,42 +345,23 @@ public function getTableMapping() { // data field values for all the available revisions without // denormalizations. case static::LAYOUT_MULTILINGUAL_REVISION: - $table_mappings[$this->baseTable] = new TableMapping(); - $table_mappings[$this->dataTable] = new TableMapping(); - $table_mappings[$this->revisionTable] = new TableMapping(); - $table_mappings[$this->revisionDataTable] = new TableMapping(); - - foreach($this->storageDefinitions as $field_name => $definition) { - $column_names = $this->getColumnNames($definition); - if (in_array($field_name, $key_fields)) { - $table_mappings[$this->baseTable]->addFieldColumns($field_name, $column_names); - } - if ($field_name != $this->uuidKey) { - $table_mappings[$this->dataTable]->addFieldColumns($field_name, $column_names); - } - } - - $table_mappings[$this->dataTable]->addDenormalizationColumn('default_langcode'); - break; - - // 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 - // data field values for all the available revisions without - // denormalizations. - case static::LAYOUT_MULTILINGUAL_REVISION: $table_mapping[$this->baseTable] = $this->getColumnMapping(array_diff($key_fields, array($this->langcodeKey))); - $data_key_fields = array_diff($key_fields, array($this->uuidKey)); - $data_fields = array_diff($storable_fields, $key_fields, $revision_metadata_fields); - $table_mapping[$this->dataTable] = $this->getColumnMapping(array_merge($data_key_fields, $data_fields)); + + // 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. As it // does not correspond to a field definition we add it with an empty // key. $table_mapping[$this->dataTable][''] = array('default_langcode'); - $table_mapping[$this->revisionTable] = $this->getColumnMapping(array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields)); - $revision_data_key_fields = array_diff($key_fields, array($this->bundleKey, $this->uuidKey)); - $revisionable_fields = array_keys(array_filter($storable_definitions, $revisionable_filter_callback)); - $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, $revision_data_key_fields); + + $revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields); + $table_mapping[$this->revisionTable] = $this->getColumnMapping($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)); // See above. $table_mapping[$this->revisionDataTable][''] = array('default_langcode'); @@ -394,9 +386,14 @@ public function getTableMapping() { protected function getColumnMapping($field_names) { $mapping = array(); foreach ($field_names as $field_name) { - $columns = isset($this->storageDefinitions[$field_name]) ? array_keys($this->storageDefinitions[$field_name]->getColumns()) : array(); - foreach ($columns as $column) { - $mapping[$field_name][] = $this->schemaHandler()->getFieldColumnName($this->storageDefinitions[$field_name], $column); + $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; @@ -505,10 +502,10 @@ protected function attachPropertyData(array &$entities) { $table_mapping = $this->getTableMapping(); $translations = array(); if ($this->revisionDataTable) { - $data_column_names = call_user_func_array('array_merge', array_diff_key($table_mapping[$this->revisionDataTable], $table_mapping[$this->baseTable])); + $data_column_names = call_user_func_array('array_merge', array_map('array_values', array_diff_key($table_mapping[$this->revisionDataTable], $table_mapping[$this->baseTable]))); } else { - $data_column_names = call_user_func_array('array_merge', $table_mapping[$this->dataTable]); + $data_column_names = call_user_func_array('array_merge', array_map('array_values', $table_mapping[$this->dataTable])); } $data_column_names = array_combine($data_column_names, $data_column_names); @@ -641,11 +638,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', $table_mapping[$this->baseTable]); + $entity_fields = call_user_func_array('array_merge', array_map('array_values', $table_mapping[$this->baseTable])); if ($this->revisionTable) { // Add all fields from the {entity_revision} table. - $entity_revision_fields = call_user_func_array('array_merge', $table_mapping[$this->revisionTable]); + $entity_revision_fields = call_user_func_array('array_merge', array_map('array_values', $table_mapping[$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]); @@ -911,20 +908,16 @@ protected function mapToStorageRecord(ContentEntityInterface $entity, $table_nam $table_mapping = $this->getTableMapping(); foreach ($table_mapping[$table_name] as $name => $storage_columns) { - if (!empty($this->fieldDefinitions[$name])) { - $definition = $this->fieldDefinitions[$name]; + 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'])) { $value = serialize($value); } - // If we are creating a new entity, we must not populate the record - // with NULL values otherwise defaults would not be applied. - if (isset($value) || !$is_new) { - list(, $storage_column) = each($storage_columns); - if (!empty($storage_column)) { - $record->$storage_column = drupal_schema_get_field_value($definition->getSchema()['columns'][$column], $value); - } + list(, $storage_column) = each($storage_columns); + if (!empty($storage_column)) { + $record->$storage_column = drupal_schema_get_field_value($definition->getSchema()['columns'][$column], $value); } } } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorageInterface.php deleted file mode 100644 index 1f31ec9..0000000 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorageInterface.php +++ /dev/null @@ -1,103 +0,0 @@ -buildFieldStorageDefinitions($entity_type_id); + $field_storage_definitions = $this->buildFieldStorageDefinitions($entity_type_id, $include_custom_storage); $this->cache->set($cid, $field_storage_definitions, Cache::PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE)); } $this->fieldStorageDefinitions[$entity_type_id] += $field_storage_definitions; @@ -525,27 +525,32 @@ public function getFieldStorageDefinitions($entity_type_id, $include_custom_stor * @param string $entity_type_id * The entity type ID. Only entity types that implement * \Drupal\Core\Entity\ContentEntityInterface are supported + * @param bool $include_custom_storage + * Whether or not to include storage definitions that have a custom storage. * * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[] * An array of field storage definitions, keyed by field name. */ - protected function buildFieldStorageDefinitions($entity_type_id) { + protected function buildFieldStorageDefinitions($entity_type_id, $include_custom_storage = TRUE) { $entity_type = $this->getDefinition($entity_type_id); $field_definitions = array(); // Retrieve base field definitions from modules. foreach ($this->moduleHandler->getImplementations('entity_field_storage_info') as $module) { + /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $module_definitions */ $module_definitions = $this->moduleHandler->invoke($module, 'entity_field_storage_info', array($entity_type)); if (!empty($module_definitions)) { // Ensure the provider key actually matches the name of the provider // defining the field. foreach ($module_definitions as $field_name => $definition) { - // @todo Remove this check once FieldDefinitionInterface exposes a - // proper provider setter. See https://drupal.org/node/2225961. - if ($definition instanceof FieldDefinition) { - $definition->setProvider($module); + if ($include_custom_storage || !$definition->hasCustomStorage()) { + // @todo Remove this check once FieldDefinitionInterface exposes a + // proper provider setter. See https://drupal.org/node/2225961. + if ($definition instanceof FieldDefinition) { + $definition->setProvider($module); + } + $field_definitions[$field_name] = $definition; } - $field_definitions[$field_name] = $definition; } } } diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php index 2ca76ad..2a58af3 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php @@ -296,7 +296,7 @@ protected function getTableMapping($table, $entity_type_id) { $schema = drupal_get_schema($table); if (!$schema && $storage instanceof SqlStorageInterface) { $mapping = $storage->getTableMapping(); - $mapping = isset($mapping[$table]) ? call_user_func_array('array_merge', $mapping[$table]) : FALSE; + $mapping = isset($mapping[$table]) ? call_user_func_array('array_merge', array_map('array_values', $mapping[$table])) : FALSE; } 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 bdd58dc..b2f203a 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -6,11 +6,9 @@ namespace Drupal\Core\Entity\Schema; -use Drupal\Core\Entity\ContentEntityDatabaseStorageInterface; +use Drupal\Core\Entity\ContentEntityDefaultDatabaseStorageInterface; use Drupal\Core\Entity\ContentEntityTypeInterface; -use Drupal\Core\Entity\ContentEntityDatabaseStorage; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Field\FieldDefinitionInterface; /** * An entity schema builder that supports revisionable, translatable entities. @@ -29,12 +27,12 @@ class ContentEntitySchemaHandler implements ContentEntitySchemaHandlerInterface * * @var \Drupal\Core\Field\FieldDefinitionInterface[] */ - protected $fieldDefinitions; + protected $storageDefinitions; /** * The storage object for the given entity type. * - * @var \Drupal\Core\Entity\ContentEntityDatabaseStorageInterface + * @var \Drupal\Core\Entity\ContentEntityDefaultDatabaseStorageInterface */ protected $storage; @@ -52,12 +50,12 @@ class ContentEntitySchemaHandler implements ContentEntitySchemaHandlerInterface * The entity manager. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type * The entity type. - * @param \Drupal\Core\Entity\ContentEntityDatabaseStorageInterface $storage + * @param \Drupal\Core\Entity\ContentEntityDefaultDatabaseStorageInterface $storage * The storage of the entity type. This must be an SQL-based storage. */ - public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorageInterface $storage) { + public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDefaultDatabaseStorageInterface $storage) { $this->entityType = $entity_type; - $this->fieldDefinitions = $entity_manager->getBaseFieldDefinitions($entity_type->id()); + $this->storageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id(), FALSE); $this->storage = $storage; } @@ -140,60 +138,102 @@ protected function getTables() { * The table schema to add the field schema to, passed by reference. * @param string $field_name * The name of the field. + * @param string[] $column_mappi + * A mapping of field column names to database column names. */ - protected function addFieldSchema(array &$schema, $field_name) { - $definition = $this->fieldDefinitions[$field_name]; - $field_schema = $definition->getSchema(); - $index = 0; + protected function addFieldSchema(array &$schema, $field_name, array $column_mapping) { + $field_schema = $this->storageDefinitions[$field_name]->getSchema(); - foreach ($field_schema['columns'] as $column_name => $column_schema) { - $schema_field_name = $this->getFieldColumnName($definition, $column_name); + foreach ($column_mapping as $field_column_name => $schema_field_name) { + $column_schema = $field_schema['columns'][$field_column_name]; $schema['fields'][$schema_field_name] = $column_schema; - $schema['fields'][$schema_field_name]['description'] = $definition->getDescription(); + $schema['fields'][$schema_field_name]['description'] = $this->storageDefinitions[$field_name]->getDescription(); // Only entity keys are required. $schema['fields'][$schema_field_name]['not null'] = (bool) $this->entityType->getKey($field_name); + } - if (!empty($field_schema['indexes'])) { - $indexes = $this->getFieldSchemaData($definition, 'indexes'); - $schema['indexes'] = !empty($schema['indexes']) ? array_merge($schema['indexes'], $indexes) : $indexes; - } + if (!empty($field_schema['indexes'])) { + $indexes = $this->getFieldIndexes($field_name, $column_mapping); + $schema['indexes'] = array_merge($schema['indexes'], $indexes); + } - if (!empty($field_schema['unique keys'])) { - $unique_keys = $this->getFieldSchemaData($definition, 'unique keys'); - $schema['unique keys'] = !empty($schema['unique keys']) ? array_merge($schema['unique keys'], $unique_keys) : $unique_keys; - } + if (!empty($field_schema['unique keys'])) { + $unique_keys = $this->getFieldUniqueKeys($field_name, $column_mapping); + $schema['unique keys'] = array_merge($schema['unique keys'], $unique_keys); + } - if (!empty($field_schema['foreign keys'])) { - $foreign_keys = $this->getFieldForeignKeys($definition); - $schema['foreign keys'] = !empty($schema['foreign keys']) ? array_merge($schema['foreign keys'], $foreign_keys) : $foreign_keys; - } + if (!empty($field_schema['foreign keys'])) { + $foreign_keys = $this->getFieldForeignKeys($field_name, $column_mapping); + $schema['foreign keys'] = array_merge($schema['foreign keys'], $foreign_keys); } } /** + * Returns an index schema array for a given field. + * + * @param string $field_name + * The name of the field. + * @param string[] $column_mappi + * A mapping of field column names to database column names. + * + * @return array + * The schema definition for the indexes. + */ + protected function getFieldIndexes($field_name, array $column_mapping) { + return $this->getFieldSchemaData('indexes', $field_name, $column_mapping); + } + + /** + * Returns a unique key schema array for a given field. + * + * @param string $field_name + * The name of the field. + * @param string[] $column_mappi + * A mapping of field column names to database column names. + * + * @return array + * The schema definition for the unique keys. + */ + protected function getFieldUniqueKeys($field_name, array $column_mapping) { + return $this->getFieldSchemaData('unique keys', $field_name, $column_mapping); + } + + /** * Returns field schema data for the given key. * - * @param \Drupal\Core\Field\FieldDefinitionInterface $definition - * The field definition. * @param string $key - * The schema key, e.g. 'indexes'. + * The schema key, e.g. 'indexes' or 'unique keys'. + * @param string $field_name + * The name of the field. + * @param string[] $column_mappi + * A mapping of field column names to database column names. * * @return array * The schema definition for the specified key. */ - protected function getFieldSchemaData(FieldDefinitionInterface $definition, $key) { + protected function getFieldSchemaData($key, $field_name, array $column_mapping) { $data = array(); - $schema = $definition->getSchema(); + $schema = $this->storageDefinitions[$field_name]->getSchema(); foreach ($schema[$key] as $key => $columns) { - $real_name = 'field__' . $this->getFieldColumnName($definition, $key); + // Similar to the database column naming, we use the field name as the + // index or unique key name for single-column fields and a combination + // of field and index or unique key name for multi-column fields. + $real_name = count($schema['columns']) == 1 + ? 'field__' . $field_name + : 'field__' . $field_name . '__' . $key; + foreach ($columns as $column) { - // Indexes can be specified as either a column name or an array with - // column name and length. Allow for either case. - $data[$real_name][] = is_array($column) ? - array($this->getFieldColumnName($definition, $column[0]), $column[1]) : - $this->getFieldColumnName($definition, $column); + // Allow for indexes and unique keys to specified as an array of column + // name and length. + if (is_array($column)) { + list($column_name, $length) = $column; + $data[$real_name][] = array($column_mapping[$column_name], $length); + } + else { + $data[$real_name][] = $column_mapping[$column]; + } } } @@ -209,16 +249,20 @@ protected function getFieldSchemaData(FieldDefinitionInterface $definition, $key * @return array * The schema definition for the foreign keys. */ - protected function getFieldForeignKeys(FieldDefinitionInterface $definition) { + protected function getFieldForeignKeys($field_name, array $column_mapping) { $foreign_keys = array(); - $schema = $definition->getSchema(); + $schema = $this->storageDefinitions[$field_name]->getSchema(); foreach ($schema['foreign keys'] as $specifier => $specification) { - $real_name = $this->getFieldIndexName($definition, $specifier); + // Similar to the database column naming, we use the field name as the + // index or unique key name for single-column fields and a combination + // of field and index or unique key name for multi-column fields. + $real_name = count($schema['columns']) == 1 + ? 'field__' . $field_name + : 'field__' . $field_name . '__' . $specifier; $foreign_keys[$real_name]['table'] = $specification['table']; foreach ($specification['columns'] as $column => $referenced) { - $sql_storage_column = $this->getFieldColumnName($definition, $column); - $foreign_keys[$real_name]['columns'][$sql_storage_column] = $referenced; + $foreign_keys[$real_name]['columns'][$column_mapping[$column]] = $referenced; } } @@ -226,25 +270,6 @@ protected function getFieldForeignKeys(FieldDefinitionInterface $definition) { } /** - * Returns the name to be used for the given field column. - * - * @param \Drupal\Core\Field\FieldDefinitionInterface $definition - * The field definition. - * - * @return string - * The column name. - */ - public function getFieldColumnName(FieldDefinitionInterface $definition, $column) { - $name = $definition->getName(); - // @todo Remove the entity reference check once we can handle schema - // changes or the Entity reference module stops messing with field - // schema. See: - // - https://drupal.org/node/1498720 - // - https://drupal.org/node/2209981 - return count($definition->getSchema()['columns']) == 1 || $definition->getType() == 'entity_reference' ? $name : $name . '__' . $column; - } - - /** * Returns the schema for the 'default_langcode' metadata field. * * @param array $schema @@ -284,7 +309,7 @@ protected function initializeBaseTable() { ); } - if ($this->storage->getLayoutType() & ContentEntityDatabaseStorage::LAYOUT_REVISION) { + if ($this->storage->getTableLayout() & ContentEntityDefaultDatabaseStorageInterface::LAYOUT_REVISION) { $revision_key = $this->entityType->getKey('revision'); $key_name = $this->getEntityIndexName($revision_key); $schema['unique keys'][$key_name] = array($revision_key); @@ -349,7 +374,7 @@ protected function initializeDataTable() { ), ); - if ($this->storage->getLayoutType() & ContentEntityDatabaseStorage::LAYOUT_REVISION) { + if ($this->storage->getTableLayout() & ContentEntityDefaultDatabaseStorageInterface::LAYOUT_REVISION) { $key = $this->entityType->getKey('revision'); $schema['indexes'][$this->getEntityIndexName($key)] = array($key); } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index 38dd8ae..1697c16 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; -use Drupal\Component\Utility\String; use Drupal\Core\Entity\TypedData\EntityDataDefinition; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldItemBase; @@ -59,10 +58,6 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel $settings = $field_definition->getSettings(); $target_type_info = \Drupal::entityManager()->getDefinition($settings['target_type']); - if (!$target_type_info) { - throw new \Exception(String::format('Target type @entity_type does not exist.', array('@target_type' => $settings['target_type']))); - } - if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { // @todo: Lookup the entity type's ID data type and use it here. // https://drupal.org/node/2107249 @@ -103,9 +98,6 @@ public static function mainPropertyName() { public static function schema(FieldStorageDefinitionInterface $field_definition) { $target_type = $field_definition->getSetting('target_type'); $target_type_info = \Drupal::entityManager()->getDefinition($target_type); - if (!$target_type_info) { - throw new \Exception(String::format('Target type @entity_type does not exist.', array('@entity_type' => $target_type))); - } if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { $columns = array( diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php index 94730f0..fac9a40 100644 --- a/core/modules/entity/entity.api.php +++ b/core/modules/entity/entity.api.php @@ -43,7 +43,6 @@ function hook_entity_schema_installed(array $storages) { * uninstalled keyed by entity type ID. */ function hook_entity_schema_uninstalled(array $storages) { - if (isset($storages['node'])) { - \Drupal::database()->schema()->dropTable('mymodule_node_data'); - } + // Delete some derived data that depends on nodes. + \Drupal::state()->delete('mymodule.node_count'); } diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php b/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php index 5ec5f0c..639db44 100644 --- a/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php +++ b/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php @@ -476,7 +476,9 @@ public function getSchema() { * {@inheritdoc} */ public function hasCustomStorage() { - return FALSE; + // @todo Fix this when multi-value fields are supported in + // https://drupal.org/node/2248977. + return TRUE; } /** diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php index f0938f4..65d0165 100644 --- a/core/modules/node/lib/Drupal/node/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php @@ -361,8 +361,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['langcode'] = FieldDefinition::create('language') ->setLabel(t('Language code')) - ->setDescription(t('The node language code.')) - ->setRevisionable(TRUE); + ->setDescription(t('The node language code.')); $fields['title'] = FieldDefinition::create('string') ->setLabel(t('Title')) @@ -458,7 +457,7 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, // displays, however, fetch the field definitions of the respective entity // type to fill in their defaults. Therefore this function ends up being // called with a non-existing bundle. - // @todo Fix this! + // @todo Fix this in https://drupal.org/node/2248795 if (!$node_type) { return $fields; } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php index 1af4d94..6b76615 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php @@ -19,7 +19,7 @@ class DrupalUnitTestBaseTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('entity', 'entity_test', 'field', 'simpletest_test'); + public static $modules = array('entity', 'entity_test', 'field', 'database_test'); public static function getInfo() { return array( @@ -33,7 +33,7 @@ public static function getInfo() { * Tests expected behavior of setUp(). */ function testSetUp() { - $tables = array('entity_test', 'simpletest_test'); + $tables = array('entity_test', 'test'); // Verify that specified $modules have been loaded. $this->assertTrue(function_exists('entity_test_permission'), 'entity_test.module was loaded.'); @@ -125,8 +125,8 @@ function testEnableModulesInstallContainer() { * Tests expected behavior of installSchema(). */ function testInstallSchema() { - $module = 'simpletest_test'; - $table = 'simpletest_test'; + $module = 'database_test'; + $table = 'test'; // Verify that we can install a table from the module schema. $this->installSchema($module, $table); $this->assertTrue(db_table_exists($table), "'$table' database table found."); diff --git a/core/modules/simpletest/tests/modules/simpletest_test/simpletest_test.info.yml b/core/modules/simpletest/tests/modules/simpletest_test/simpletest_test.info.yml deleted file mode 100644 index 6c98069..0000000 --- a/core/modules/simpletest/tests/modules/simpletest_test/simpletest_test.info.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: Simpletest Test -type: module -description: 'Provides dummy hook implementations for use by SimpleTest tests.' -package: Testing -version: VERSION -core: 8.x -hidden: TRUE diff --git a/core/modules/simpletest/tests/modules/simpletest_test/simpletest_test.install b/core/modules/simpletest/tests/modules/simpletest_test/simpletest_test.install deleted file mode 100644 index f9b16a0..0000000 --- a/core/modules/simpletest/tests/modules/simpletest_test/simpletest_test.install +++ /dev/null @@ -1,50 +0,0 @@ - 'Stores simpltest_test data.', - 'fields' => array( - 'sid' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique ID.', - ), - 'uuid' => array( - 'description' => 'Unique Key: Universally unique identifier for this entity.', - 'type' => 'varchar', - 'length' => 128, - 'not null' => FALSE, - ), - 'dummy_textfield' => array( - 'type' => 'varchar', - 'length' => 64, - 'not null' => TRUE, - 'default' => '', - 'description' => 'A Dummy text field.', - ), - 'dummy_integer' => array( - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A dummy integer field.', - ), - ), - 'indexes' => array( - 'simpltest_test_index' => array('sid', 'dummy_integer'), - ), - 'primary key' => array('sid'), - 'unique keys' => array( - 'uuid' => array('uuid'), - ), - ); - - return $schema; -} diff --git a/core/modules/user/lib/Drupal/user/AccountFormController.php b/core/modules/user/lib/Drupal/user/AccountFormController.php index 75b71dd..7993e75 100644 --- a/core/modules/user/lib/Drupal/user/AccountFormController.php +++ b/core/modules/user/lib/Drupal/user/AccountFormController.php @@ -367,7 +367,7 @@ public function validate(array $form, array &$form_state) { $form_state['values']['signature'] = $form_state['values']['signature']['value']; // @todo Make the user signature field use a widget to benefit from - // automatic typed data validation. + // automatic typed data validation in https://drupal.org/node/2227381. $field_definitions = $this->entityManager->getFieldDefinitions('user', $this->getEntity()->bundle()); $max_length = $field_definitions['signature']->getSetting('max_length'); if (drupal_strlen($form_state['values']['signature']) > $max_length) { diff --git a/core/modules/user/lib/Drupal/user/Entity/User.php b/core/modules/user/lib/Drupal/user/Entity/User.php index 071e402..22492f2 100644 --- a/core/modules/user/lib/Drupal/user/Entity/User.php +++ b/core/modules/user/lib/Drupal/user/Entity/User.php @@ -504,7 +504,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['status'] = FieldDefinition::create('boolean') ->setLabel(t('User status')) ->setDescription(t('Whether the user is active or blocked.')) - // @todo This should be FALSE. + // @todo As the status has access implications users should be created as + // blocked by default and activated explicitly if needed. See + // https://drupal.org/node/2248969. ->setSetting('default_value', TRUE); $fields['created'] = FieldDefinition::create('created') diff --git a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php index a446ff1..b7373c2 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php @@ -6,7 +6,7 @@ namespace Drupal\Tests\Core\Entity\Schema; -use Drupal\Core\Entity\ContentEntityDatabaseStorageInterface; +use Drupal\Core\Entity\ContentEntityDefaultDatabaseStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\Schema\ContentEntitySchemaHandler; use Drupal\Tests\UnitTestCase; @@ -38,16 +38,16 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { /** * The mocked SQL storage used in this test. * - * @var \Drupal\Core\Entity\ContentEntityDatabaseStorageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\ContentEntityDefaultDatabaseStorageInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $sqlStorage; + protected $storage; /** * The mocked field definitions used in this test. * - * @var \Drupal\Core\Field\FieldDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[] + * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[] */ - protected $fieldDefinitions; + protected $storageDefinitions; /** * The content entity schema handler used in this test. @@ -73,14 +73,14 @@ public static function getInfo() { public function setUp() { $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $this->entityType = $this->getMock('Drupal\Core\Entity\ContentEntityTypeInterface'); - $this->sqlStorage = $this->getMock('Drupal\Core\Entity\ContentEntityDatabaseStorageInterface'); + $this->storage = $this->getMock('Drupal\Core\Entity\ContentEntityDefaultDatabaseStorageInterface'); // Set up basic expectations that apply to all tests. $this->entityType->expects($this->any()) ->method('id') ->will($this->returnValue('entity_test')); - $this->sqlStorage->expects($this->any()) + $this->storage->expects($this->any()) ->method('getBaseTable') ->will($this->returnValue('entity_test')); } @@ -101,22 +101,22 @@ public function testGetSchemaLayoutBase() { array('uuid', NULL), ))); - $this->sqlStorage->expects($this->once()) - ->method('getLayoutType') - ->will($this->returnValue(ContentEntityDatabaseStorageInterface::LAYOUT_BASE)); + $this->storage->expects($this->once()) + ->method('getTableLayout') + ->will($this->returnValue(ContentEntityDefaultDatabaseStorageInterface::LAYOUT_BASE)); $this->setupBaseLayoutFieldDefinitions(); $this->setupSchemaHandler(); - $this->sqlStorage->expects($this->once()) + $this->storage->expects($this->once()) ->method('getTableMapping') ->will($this->returnValue( array( 'entity_test' => array( - 'id' => array('id'), - 'name' => array('name'), - 'type' => array('type'), + 'id' => array('value' => 'id'), + 'name' => array('value' => 'name'), + 'type' => array('value' => 'type'), ), ) )); @@ -169,12 +169,12 @@ public function testGetSchemaLayoutBaseWithUuid() { array('uuid', 'uuid'), ))); - $this->sqlStorage->expects($this->once()) - ->method('getLayoutType') - ->will($this->returnValue(ContentEntityDatabaseStorageInterface::LAYOUT_BASE)); + $this->storage->expects($this->once()) + ->method('getTableLayout') + ->will($this->returnValue(ContentEntityDefaultDatabaseStorageInterface::LAYOUT_BASE)); $this->setupBaseLayoutFieldDefinitions(); - $this->setupFieldDefinition('uuid', array( + $this->setupStorageDefinition('uuid', array( 'columns' => array( 'value' => array( 'type' => 'varchar', @@ -185,15 +185,15 @@ public function testGetSchemaLayoutBaseWithUuid() { $this->setupSchemaHandler(); - $this->sqlStorage->expects($this->once()) + $this->storage->expects($this->once()) ->method('getTableMapping') ->will($this->returnValue( array( 'entity_test' => array( - 'id' => array('id'), - 'uuid' => array('uuid'), - 'name' => array('name'), - 'type' => array('type'), + 'id' => array('value' => 'id'), + 'uuid' => array('value' => 'uuid'), + 'name' => array('value' => 'name'), + 'type' => array('value' => 'type'), ), ) )); @@ -244,7 +244,7 @@ public function testGetSchemaLayoutBaseWithUuid() { */ protected function setupBaseLayoutFieldDefinitions() { // @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem::schema() - $this->setupFieldDefinition('id', array( + $this->setupStorageDefinition('id', array( 'columns' => array( 'value' => array( 'type' => 'int', @@ -252,7 +252,7 @@ protected function setupBaseLayoutFieldDefinitions() { ), )); // @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem::schema() - $this->setupFieldDefinition('name', array( + $this->setupStorageDefinition('name', array( 'columns' => array( 'value' => array( 'type' => 'varchar', @@ -261,7 +261,7 @@ protected function setupBaseLayoutFieldDefinitions() { ), )); // @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem::schema() - $this->setupFieldDefinition('type', array( + $this->setupStorageDefinition('type', array( 'columns' => array( 'value' => array( 'type' => 'varchar', @@ -278,13 +278,13 @@ protected function setupBaseLayoutFieldDefinitions() { */ public function setupSchemaHandler() { $this->entityManager->expects($this->once()) - ->method('getBaseFieldDefinitions') + ->method('getFieldStorageDefinitions') ->with('entity_test') - ->will($this->returnValue($this->fieldDefinitions)); + ->will($this->returnValue($this->storageDefinitions)); $this->schemaHandler = new ContentEntitySchemaHandler( $this->entityManager, $this->entityType, - $this->sqlStorage + $this->storage ); } @@ -297,17 +297,12 @@ public function setupSchemaHandler() { * The schema array of the field definition, as returned from * FieldDefinitionInterface::schema(). */ - public function setupFieldDefinition($field_name, array $schema) { - $this->fieldDefinitions[$field_name] = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); - $this->fieldDefinitions[$field_name]->expects($this->once()) - ->method('getName') - ->will($this->returnValue($field_name)); - $this->fieldDefinitions[$field_name]->expects($this->once()) + public function setupStorageDefinition($field_name, array $schema) { + $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $this->storageDefinitions[$field_name]->expects($this->once()) ->method('getDescription') ->will($this->returnValue("The $field_name field.")); - // @todo ContentEntitySchemaHandler should be fixed to only call getSchema() - // once per field definition. - $this->fieldDefinitions[$field_name]->expects($this->exactly(2)) + $this->storageDefinitions[$field_name]->expects($this->exactly(1)) ->method('getSchema') ->will($this->returnValue($schema)); }