diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 184c767..35883d7 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -28,7 +28,6 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass; use Drupal\Core\Plugin\PluginManagerPass; use Drupal\Core\Theme\ThemeNegotiatorPass; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Scope; @@ -54,6 +53,7 @@ public function register(ContainerBuilder $container) { // object and get reconstructed when the request object changes (e.g., // during a subrequest). $container->addScope(new Scope('request')); + $this->registerTwig($container); $this->registerUuid($container); diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index c6ff65c..0ac5322 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -211,7 +211,7 @@ public function isTranslatable() { /** * {@inheritdoc} */ - public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, array $values) { } /** diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php index 31b1613..eaa6390 100644 --- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php @@ -8,7 +8,10 @@ namespace Drupal\Core\Entity; use Drupal\Core\Database\Connection; +use Drupal\Core\Database\Database; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\Core\Entity\Schema\SchemaBuilder; +use Drupal\Core\Entity\Schema\SchemaBuilderInterface; use Drupal\Core\Language\Language; use Drupal\field\FieldInfo; use Drupal\field\FieldConfigUpdateForbiddenException; @@ -25,7 +28,7 @@ * This class can be used as-is by most simple entity types. Entity types * requiring special handling can extend the class. */ -class FieldableDatabaseStorageController extends FieldableEntityStorageControllerBase { +class FieldableDatabaseStorageController extends FieldableEntityStorageControllerBase implements SqlStorageControllerInterface { /** * Name of entity's revision database table field, if it supports revisions. @@ -37,6 +40,13 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle protected $revisionKey = FALSE; /** + * The base table of the entity, if the entity has storage. + * + * @var string + */ + protected $baseTable; + + /** * The table that stores revisions, if the entity supports revisions. * * @var string @@ -79,13 +89,28 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle protected $fieldInfo; /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * The entity schema builder. + * + * @var \Drupal\Core\Entity\Schema\SchemaBuilderInterface + */ + protected $schemaBuilder; + + /** * {@inheritdoc} */ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { return new static( $entity_type, $container->get('database'), - $container->get('field.info') + $container->get('field.info'), + $container->get('entity.manager') ); } @@ -98,12 +123,15 @@ public static function createInstance(ContainerInterface $container, EntityTypeI * The database connection to be used. * @param \Drupal\field\FieldInfo $field_info * The field info service. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, FieldInfo $field_info) { + public function __construct(EntityTypeInterface $entity_type, Connection $database, FieldInfo $field_info, EntityManagerInterface $entity_manager) { parent::__construct($entity_type); $this->database = $database; $this->fieldInfo = $field_info; + $this->entityManager = $entity_manager; // Check if the entity type supports IDs. if ($this->entityType->hasKey('id')) { @@ -113,6 +141,11 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa // Check if the entity type supports UUIDs. $this->uuidKey = $this->entityType->getKey('uuid'); + // Some entity types are not stored in the database at all. + if ($base_table = $this->entityType->getBaseTable()) { + $this->baseTable = $base_table; + } + // Check if the entity type supports revisions. if ($this->entityType->hasKey('revision')) { $this->revisionKey = $this->entityType->getKey('revision'); @@ -120,12 +153,14 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa } // Check if the entity type has a dedicated table for fields. + // @todo Replace this with a check for the 'language' entity key when + // https://drupal.org/node/2143729 has landed. if ($data_table = $this->entityType->getDataTable()) { $this->dataTable = $data_table; // Entity types having both revision and translation support should always // define a revision data table. - if ($this->revisionTable && $revision_data_table = $this->entityType->getRevisionDataTable()) { - $this->revisionDataTable = $revision_data_table; + if ($this->revisionTable) { + $this->revisionDataTable = $this->entityType->getRevisionDataTable(); } } } @@ -209,28 +244,45 @@ public function load($id) { */ protected function mapFromStorageRecords(array $records) { $entities = array(); - foreach ($records as $id => $record) { + $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId); + foreach ($records as $id => $values) { $entities[$id] = array(); // Skip the item delta and item value levels (if possible) but let the // field assign the value as suiting. This avoids unnecessary array // hierarchies and saves memory here. - foreach ($record as $name => $value) { + foreach ($values as $name => $value) { // Handle columns named [field_name]__[column_name] (e.g for field types // that store several properties). if ($field_name = strstr($name, '__', TRUE)) { $property_name = substr($name, strpos($name, '__') + 2); - $entities[$id][$field_name][Language::LANGCODE_DEFAULT][$property_name] = $value; + + $field_definition = $field_definitions[$field_name]; + $serialize = !empty($field_definition->getColumns()[$property_name]['serialize']); + $entities[$id][$field_name][Language::LANGCODE_DEFAULT][$property_name] = $serialize ? unserialize($value) : $value; } else { // Handle columns named directly after the field (e.g if the field - // type only stores one property). - $entities[$id][$name][Language::LANGCODE_DEFAULT] = $value; + // type only stores one property). We allow for storage controllers to + // have schema fields which are not entity fields, which can be useful + // for normalization purposes. The 'default_langcode' field, for + // example, is simply a denormalization of checking the language code + // in the data table against the language code in the base table and + // similarly the 'isDefaultRevision' field (which we add as a query + // expression in FieldableDatabaseStorageController::buildQuery()) is + // a denormalization of checking the revision ID in the revision table + // against the revision ID in the base table. + $serialize = FALSE; + if (isset($field_definitions[$name])) { + $field_definition = $field_definitions[$name]; + $serialize = !empty($field_definition->getColumns()[$field_definition->getMainPropertyName()]['serialize']); + } + $entities[$id][$name][Language::LANGCODE_DEFAULT] = $serialize ? unserialize($value) : $value; } } - // If we have no multilingual values we can instantiate entity objecs + // If we have no multilingual values we can instantiate entity objects // right now, otherwise we need to collect all the field values first. if (!$this->dataTable) { - $bundle = $this->bundleKey ? $record->{$this->bundleKey} : FALSE; + $bundle = $this->bundleKey ? $values[$this->bundleKey] : FALSE; // Turn the record into an entity class. $entities[$id] = new $this->entityClass($entities[$id], $this->entityTypeId, $bundle); } @@ -247,6 +299,8 @@ protected function mapFromStorageRecords(array $records) { */ protected function attachPropertyData(array &$entities) { if ($this->dataTable) { + $table_mapping = $this->getTableMapping(); + // If a revision table is available, we need all the properties of the // latest revision. Otherwise we fall back to the data table. $table = $this->revisionDataTable ?: $this->dataTable; @@ -265,13 +319,14 @@ protected function attachPropertyData(array &$entities) { } $data = $query->execute(); - $field_definitions = \Drupal::entityManager()->getBaseFieldDefinitions($this->entityTypeId); + $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId); + $translations = array(); if ($this->revisionDataTable) { - $data_column_names = array_flip(array_diff(drupal_schema_fields_sql($this->entityType->getRevisionDataTable()), drupal_schema_fields_sql($this->entityType->getBaseTable()))); + $data_column_names = array_diff($table_mapping['revision_data_table'], $table_mapping['base_table']); } else { - $data_column_names = array_flip(drupal_schema_fields_sql($this->entityType->getDataTable())); + $data_column_names = $table_mapping['data_table']; } foreach ($data as $values) { @@ -391,6 +446,8 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value * A SelectQuery object for loading the entity. */ protected function buildQuery($ids, $revision_id = FALSE) { + $table_mapping = $this->getTableMapping(); + $query = $this->database->select($this->entityType->getBaseTable(), 'base'); $query->addTag($this->entityTypeId . '_load_multiple'); @@ -403,12 +460,11 @@ protected function buildQuery($ids, $revision_id = FALSE) { } // Add fields from the {entity} table. - $entity_fields = drupal_schema_fields_sql($this->entityType->getBaseTable()); + $entity_fields = $table_mapping['base_table']; if ($this->revisionTable) { // Add all fields from the {entity_revision} table. - $entity_revision_fields = drupal_schema_fields_sql($this->entityType->getRevisionTable()); - $entity_revision_fields = array_combine($entity_revision_fields, $entity_revision_fields); + $entity_revision_fields = array_combine($table_mapping['revision_table'], $table_mapping['revision_table']); // The ID field is provided by entity, so remove it. unset($entity_revision_fields[$this->idKey]); @@ -548,11 +604,16 @@ public function save(EntityInterface $entity) { $this->invokeHook('presave', $entity); // Create the storage record to be saved. - $record = $this->mapToStorageRecord($entity); + $values = $this->mapToStorageRecord($entity); if (!$entity->isNew()) { if ($entity->isDefaultRevision()) { - $return = drupal_write_record($this->entityType->getBaseTable(), $record, $this->idKey); + $this->database + ->update($this->baseTable) + ->fields($values) + ->condition($this->idKey, $values[$this->idKey]) + ->execute(); + $return = SAVED_UPDATED; } else { // @todo, should a different value be returned when saving an entity @@ -560,7 +621,7 @@ public function save(EntityInterface $entity) { $return = FALSE; } if ($this->revisionTable) { - $record->{$this->revisionKey} = $this->saveRevision($entity); + $values[$this->revisionKey] = $this->saveRevision($entity); } if ($this->dataTable) { $this->savePropertyData($entity); @@ -582,11 +643,20 @@ public function save(EntityInterface $entity) { // Ensure the entity is still seen as new after assigning it an id, // while storing its data. $entity->enforceIsNew(); - $return = drupal_write_record($this->entityType->getBaseTable(), $record); - $entity->{$this->idKey}->value = (string) $record->{$this->idKey}; + $insert_id = $this->database + ->insert($this->baseTable, array('return' => Database::RETURN_INSERT_ID)) + ->fields($values) + ->execute(); + // Even if this is a new entity, the ID key might have been set in which + // case we should not override the provided ID. + if (!isset($values[$this->idKey])) { + $values[$this->idKey] = $insert_id; + } + $return = SAVED_NEW; + $entity->{$this->idKey}->value = (string) $values[$this->idKey]; if ($this->revisionTable) { $entity->setNewRevision(); - $record->{$this->revisionKey} = $this->saveRevision($entity); + $values[$this->revisionKey] = $this->saveRevision($entity); } if ($this->dataTable) { $this->savePropertyData($entity); @@ -595,7 +665,6 @@ public function save(EntityInterface $entity) { $this->savePropertyData($entity, 'revision_data_table'); } - $entity->enforceIsNew(FALSE); $this->invokeFieldMethod('insert', $entity); $this->saveFieldItems($entity, FALSE); @@ -644,8 +713,7 @@ protected function savePropertyData(EntityInterface $entity, $table_key = 'data_ foreach ($entity->getTranslationLanguages() as $langcode => $language) { $translation = $entity->getTranslation($langcode); - $record = $this->mapToDataStorageRecord($translation, $table_key); - $values = (array) $record; + $values = $this->mapToDataStorageRecord($translation, $table_key); $query ->fields(array_keys($values)) ->values($values); @@ -657,41 +725,51 @@ protected function savePropertyData(EntityInterface $entity, $table_key = 'data_ /** * Maps from an entity object to the storage record. * - * @param \Drupal\Core\Entity\EntityInterface $entity + * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. * @param string $table_key * (optional) The entity key identifying the target table. Defaults to * 'base_table'. * - * @return \stdClass - * The record to store. + * @return array + * An array of values to store. */ - protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'base_table') { - $record = new \stdClass(); + protected function mapToStorageRecord(ContentEntityInterface $entity, $table_key = 'base_table') { $values = array(); $definitions = $entity->getFieldDefinitions(); - $schema = drupal_get_schema($this->entityType->get($table_key)); + + $table_name = $this->entityType->get($table_key); + $schema = $this->schemaBuilder()->getSchema($this->entityType, $definitions)[$table_name]; + $is_new = $entity->isNew(); $multi_column_fields = array(); - foreach (drupal_schema_fields_sql($this->entityType->get($table_key)) as $name) { + foreach (array_keys($schema['fields']) as $name) { // Check for fields which store data in multiple columns and process them // separately. if ($field = strstr($name, '__', TRUE)) { $multi_column_fields[$field] = TRUE; continue; } - $values[$name] = isset($definitions[$name]) && isset($entity->$name->value) ? $entity->$name->value : NULL; + $value = isset($definitions[$name]) && isset($entity->$name->value) ? $entity->$name->value : NULL; + + // Since this is not a multi-column field we can assume there is only one + // column. + $definition = $definitions[$name]; + $serialize = !empty($definition->getColumns()[$definition->getMainPropertyName()]['serialize']); + $values[$name] = $serialize ? serialize($value) : $value; } // Handle fields that store multiple properties and match each property name // to its schema column name. foreach (array_keys($multi_column_fields) as $field_name) { + /** @var \Drupal\Core\Field\FieldItemListInterface $field_items */ $field_items = $entity->get($field_name); $field_value = $field_items->getValue(); - foreach (array_keys($field_items->getFieldDefinition()->getColumns()) as $field_schema_column) { - if (isset($schema['fields'][$field_name . '__' . $field_schema_column])) { - $values[$field_name . '__' . $field_schema_column] = isset($field_value[0][$field_schema_column]) ? $field_value[0][$field_schema_column] : NULL; + foreach ($field_items->getFieldDefinition()->getColumns() as $column_name => $column_info) { + if (isset($schema['fields'][$field_name . '__' . $column_name])) { + $value = isset($field_value[0][$column_name]) ? $field_value[0][$column_name] : NULL; + $values[$field_name . '__' . $column_name] = !empty($column_info['serialize']) ? serialize($value) : $value; } } } @@ -700,7 +778,7 @@ protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'bas // 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) { - $record->$field_name = drupal_schema_get_field_value($schema['fields'][$field_name], $value); + $record[$field_name] = drupal_schema_get_field_value($schema['fields'][$field_name], $value); } } @@ -710,59 +788,71 @@ protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'bas /** * Maps from an entity object to the storage record of the field data. * - * @param \Drupal\Core\Entity\EntityInterface $entity + * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. * @param string $table_key * (optional) The entity key identifying the target table. Defaults to * 'data_table'. * - * @return \stdClass - * The record to store. + * @return array + * An array of values to store. */ - protected function mapToDataStorageRecord(EntityInterface $entity, $table_key = 'data_table') { - $record = $this->mapToStorageRecord($entity, $table_key); - $record->langcode = $entity->language()->id; - $record->default_langcode = intval($record->langcode == $entity->getUntranslated()->language()->id); - return $record; + protected function mapToDataStorageRecord(ContentEntityInterface $entity, $table_key = 'data_table') { + $values = $this->mapToStorageRecord($entity, $table_key); + $values['langcode'] = $entity->language()->id; + $values['default_langcode'] = intval($values['langcode'] == $entity->getUntranslated()->language()->id); + return $values; } /** * Saves an entity revision. * - * @param \Drupal\Core\Entity\EntityInterface $entity + * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. * * @return int * The revision id. */ - protected function saveRevision(EntityInterface $entity) { - $record = $this->mapToStorageRecord($entity, 'revision_table'); + protected function saveRevision(ContentEntityInterface $entity) { + $values = $this->mapToStorageRecord($entity, 'revision_table'); // When saving a new revision, set any existing revision ID to NULL so as to // ensure that a new revision will actually be created. - if ($entity->isNewRevision() && isset($record->{$this->revisionKey})) { - $record->{$this->revisionKey} = NULL; + if ($entity->isNewRevision() && isset($values[$this->revisionKey])) { + $values[$this->revisionKey] = NULL; } - $entity->preSaveRevision($this, $record); + $entity->preSaveRevision($this, $values); if ($entity->isNewRevision()) { - drupal_write_record($this->revisionTable, $record); + $insert_id = $this->database + ->insert($this->revisionTable, array('return' => Database::RETURN_INSERT_ID)) + ->fields($values) + ->execute(); + // Even if this is a new revsision, the revision ID key might have been + // set in which case we should not override the provided revision ID. + if (!isset($values[$this->revisionKey])) { + $values[$this->revisionKey] = $insert_id;; + } if ($entity->isDefaultRevision()) { $this->database->update($this->entityType->getBaseTable()) - ->fields(array($this->revisionKey => $record->{$this->revisionKey})) - ->condition($this->idKey, $record->{$this->idKey}) + ->fields(array($this->revisionKey => $values[$this->revisionKey])) + ->condition($this->idKey, $values[$this->idKey]) ->execute(); } } else { - drupal_write_record($this->revisionTable, $record, $this->revisionKey); + $this->database + ->update($this->revisionTable) + ->fields($values) + ->condition($this->revisionKey, $values[$this->revisionKey]) + ->execute(); } // Make sure to update the new revision key for the entity. - $entity->{$this->revisionKey}->value = $record->{$this->revisionKey}; + $entity->{$this->revisionKey}->value = $values[$this->revisionKey]; - return $record->{$this->revisionKey}; + return $values[$this->revisionKey]; } /** @@ -775,6 +865,13 @@ public function getQueryServiceName() { /** * {@inheritdoc} */ + public function getSchemaBuilderServiceName() { + return 'entity.schema_builder.default'; + } + + /** + * {@inheritdoc} + */ protected function doLoadFieldItems($entities, $age) { $load_current = $age == static::FIELD_LOAD_CURRENT; @@ -893,7 +990,7 @@ protected function doSaveFieldItems(EntityInterface $entity, $update) { foreach ($items as $delta => $item) { // We now know we have someting to insert. $do_insert = TRUE; - $record = array( + $values = array( 'entity_id' => $id, 'revision_id' => $vid, 'bundle' => $bundle, @@ -903,10 +1000,10 @@ protected function doSaveFieldItems(EntityInterface $entity, $update) { foreach ($field->getColumns() as $column => $attributes) { $column_name = static::_fieldColumnName($field, $column); // Serialize the value if specified in the column schema. - $record[$column_name] = !empty($attributes['serialize']) ? serialize($item->$column) : $item->$column; + $values[$column_name] = !empty($attributes['serialize']) ? serialize($item->$column) : $item->$column; } - $query->values($record); - $revision_query->values($record); + $query->values($values); + $revision_query->values($values); if ($field->getCardinality() != FieldConfigInterface::CARDINALITY_UNLIMITED && ++$delta_count == $field->getCardinality()) { break; @@ -1456,4 +1553,28 @@ static public function _fieldColumnName(FieldConfigInterface $field, $column) { return in_array($column, FieldConfig::getReservedColumns()) ? $column : $field->getName() . '_' . $column; } + /** + * Gets the schema builder for this storage controller. + */ + protected function schemaBuilder() { + if (!isset($this->schemaBuilder)) { + $this->schemaBuilder = new SchemaBuilder($this->entityManager, $this->entityType); + } + return $this->schemaBuilder; + } + + /** + * {@inheritdoc} + */ + public function getTableMapping() { + return $this->schemaBuilder()->getTableMapping(); + } + + /** + * {@inheritdoc} + */ + public function getSchema() { + return $this->schemaBuilder()->getSchema(); + } + } diff --git a/core/lib/Drupal/Core/Entity/RevisionableInterface.php b/core/lib/Drupal/Core/Entity/RevisionableInterface.php index d246d24..b329ab3 100644 --- a/core/lib/Drupal/Core/Entity/RevisionableInterface.php +++ b/core/lib/Drupal/Core/Entity/RevisionableInterface.php @@ -58,9 +58,9 @@ public function isDefaultRevision($new_value = NULL); * * @param EntityStorageControllerInterface $storage_controller * The entity storage controller object. - * @param \stdClass $record - * The revision object. + * @param array $values + * An array of values to save, keyed by field name. */ - public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record); + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, array $values); } diff --git a/core/lib/Drupal/Core/Entity/Schema/SchemaBuilder.php b/core/lib/Drupal/Core/Entity/Schema/SchemaBuilder.php new file mode 100644 index 0000000..bc234ee --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Schema/SchemaBuilder.php @@ -0,0 +1,402 @@ +entityType = $entity_type; + $this->fieldDefinitions = $entity_manager->getBaseFieldDefinitions($entity_type->id()); + } + + /** + * {@inheritdoc} + */ + public function getTableMapping() { + if (!isset($this->tableMapping)) { + $tables = $this->getTables(); + + if (empty($tables)) { + $table_mapping = array(); + } + elseif ($base_schema = drupal_get_schema($tables['base_table'])) { + $table_mapping['base_table'] = array_keys($base_schema['fields']); + if (isset($tables['revision_table'])) { + $table_mapping['revision_table'] = array_keys(drupal_get_schema($tables['revision_table'])['fields']); + } + if (isset($tables['data_table'])) { + $table_mapping['data_table'] = array_keys(drupal_get_schema($tables['data_table'])['fields']); + } + if (isset($tables['revision_data_table'])) { + $table_mapping['revision_data_table'] = array_keys(drupal_get_schema($tables['revision_data_table'])['fields']); + } + } + else { + $entity_keys = array_filter(array( + 'id' => $this->entityType->getKey('id'), + 'revision' => $this->entityType->getKey('revision'), + 'bundle' => $this->entityType->getKey('bundle'), + 'uuid' => $this->entityType->getKey('uuid'), + )); + // @todo This will become unnecessary once https://drupal.org/node/2143729 + // has landed. + if (isset($tables['data_table'])) { + $entity_keys['language'] = 'langcode'; + $entity_keys['default_language'] = 'default_langcode'; + } + + $storable_definitions = array_filter($this->fieldDefinitions, function (FieldDefinitionInterface $field_definition) { + return !$field_definition->isComputed() && !$field_definition->hasCustomStorage(); + }); + $other_definitions = array_diff_key($storable_definitions, array_flip($entity_keys) + array('langcode' => TRUE)); + $other_fields = array_keys($other_definitions); + + // ID, revision ID, bundle and UUID get stored in all tables. Using + // $tables and $entity_keys instead of building the array manually avoids + // checks for which tables and which entity keys actually exist for the + // given entity type. + $table_mapping = array_fill_keys(array_keys($tables), array_values($entity_keys)); + if (!isset($tables['data_table'])) { + // Non-translatable entity types store all data in the base table and + // the revision table, if it exists. Non-revisionable fields are also + // denormalized in the revision tables to make queries for revisions + // more performant. + $table_mapping['base_table'] = array_merge($table_mapping['base_table'], $other_fields); + if (isset($tables['revision_table'])) { + $table_mapping['revision_table'] = array_merge($table_mapping['revision_table'], $other_fields); + } + } + else { + // Translatable entity types also store 'langcode' on all tables and + // 'default_language' on the data table and the revision data table, if + // exists. + $table_mapping = array_merge_recursive($table_mapping, array_fill_keys(array_keys($tables), array('language'))); + $table_mapping['data_table'][] = 'default_language'; + + // Translatable entity types store all data apart from the entity key + // fields in the data table and the revision data table, if it exists. + // Untranslatable fields are also stored in the data table(s) to make + // queries more performant. + $table_mapping['data_table'] = array_merge($table_mapping['data_table'], $other_fields); + if (isset($tables['revision_data_table'])) { + $table_mapping['revision_data_table'][] = 'default_language'; + $table_mapping['revision_data_table'] = array_merge($table_mapping['revision_data_table'], $other_fields); + } + } + } + + + $this->tableMapping = $table_mapping; + } + + return $this->tableMapping; + } + + /** + * {@inheritdoc} + */ + public function getSchema() { + // Prepare basic information about the entity type. + $tables = $this->getTables($this->entityType); + // If this entity type does not support storage, no schema information can + // be collected. + if (empty($tables)) { + $this->schema[$this->entityType->id()] = NULL; + } + + if (!isset($this->schema[$this->entityType->id()])) { + // @todo Remove this. This is currently needed because some entity types + // declare a schema that does not match their field definitions. + // Therefore we use the legacy schema if it's still declared. This + // allows to iteratively enable the automatic schema generation for + // all entity types. + if ($base_schema = drupal_get_schema($tables['base_table'])) { + $schema[$tables['base_table']] = $base_schema; + unset($tables['base_table']); + foreach ($tables as $table) { + $schema[$table] = drupal_get_schema($table); + } + } + + else { + // Initialize the table schema. + $schema[$tables['base_table']] = $this->initializeBaseTable(); + if (isset($tables['revision_table'])) { + $schema[$tables['revision_table']] = $this->initializeRevisionTable(); + } + if (isset($tables['data_table'])) { + $schema[$tables['data_table']] = $this->initializeDataTable(); + } + if (isset($tables['revision_data_table'])) { + $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable(); + } + + // Add the schema from field definitions. + foreach ($this->getTableMapping() as $table_key => $field_names) { + foreach ($field_names as $field_name) { + $this->addFieldSchema($schema[$tables[$table_key]], $field_name, $this->fieldDefinitions[$field_name]); + } + } + + // Process tables after having gathered field information. + $this->processBaseTable($schema[$tables['base_table']]); + if (isset($tables['revision_table'])) { + $this->processRevisionTable($schema[$tables['revision_table']]); + } + if (isset($tables['data_table'])) { + $this->processDataTable($schema[$tables['data_table']]); + } + if (isset($tables['revision_data_table'])) { + $this->processRevisionDataTable($schema[$tables['revision_data_table']]); + } + } + + $this->schema[$this->entityType->id()] = $schema; + } + + return $this->schema[$this->entityType->id()]; + } + + /** + * Gets a list of entity type tables. + * + * @return array + * A list of entity type tables, keyed by table key. + */ + protected function getTables() { + return array_filter(array( + 'base_table' => $this->entityType->getBaseTable(), + 'revision_table' => $this->entityType->getRevisionTable(), + 'data_table' => $this->entityType->getDataTable(), + 'revision_data_table' => $this->entityType->getRevisionDataTable(), + )); + } + + /** + * Returns the schema for a single field definition. + * + * @param array $schema + * The table schema to add the field schema to, passed by reference. + * @param string $field_name + * The name of the field. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition to return the schema for. + */ + protected function addFieldSchema(array &$schema, $field_name, FieldDefinitionInterface $field_definition) { + $field_schema = $field_definition->getSchema(); + if (count($field_schema['columns']) == 1) { + $schema['fields'][$field_name] = $field_schema['columns'][$field_definition->getMainPropertyName()]; + $schema['fields'][$field_name]['description'] = $field_definition->getDescription(); + + if (!empty($field_schema['unique keys'])) { + $schema['unique keys'][$field_name] = array($field_name); + } + if (!empty($field_schema['indexes'])) { + // @todo Support indexes specified as an array of column name and + // length. + $schema['indexes'][$field_name] = array($field_name); + } + if (!empty($field_schema['foreign keys'])) { + $schema += array('foreign keys' => array()); + $schema['foreign keys'] += $field_schema['foreign keys']; + } + } + else { + foreach ($field_schema['columns'] as $column_name => $column_schema) { + $schema['fields'][$field_name . '__' . $column_name] = $column_schema; + } + + // @todo Descriptions, unique keys, indexes and foreign keys. + } + } + + /** + * Initializes common information for a base table. + * + * @return array + * A partial schema array for the base table. + */ + protected function initializeBaseTable() { + return array( + 'description' => "The base table for {$this->entityType->id()} entities.", + 'primary key' => array($this->entityType->getKey('id')), + ); + } + + /** + * Initializes common information for a revision table. + * + * @return array + * A partial schema array for the revision table. + */ + protected function initializeRevisionTable() { + $id_key = $this->entityType->getKey('id'); + return array( + 'description' => "The revision table for {$this->entityType->id()} entities.", + 'primary key' => array($this->entityType->getKey('revision')), + 'foreign keys' => array( + $id_key => array( + 'table' => $this->entityType->getBaseTable(), + 'columns' => array($id_key => $id_key), + ), + ), + ); + } + + /** + * Initializes common information for a data table. + * + * @return array + * A partial schema array for the data table. + */ + protected function initializeDataTable() { + $id_key = $this->entityType->getKey('id'); + return array( + 'description' => "The data table for {$this->entityType->id()} entities.", + 'primary key' => array($id_key, $this->entityType->getKey('language')), + 'foreign keys' => array( + $id_key => array( + 'table' => $this->entityType->getBaseTable(), + 'columns' => array($id_key => $id_key), + ), + ), + ); + } + + /** + * Initializes common information for a revision data table. + * + * @return array + * A partial schema array for the revision data table. + */ + protected function initializeRevisionDataTable() { + $id_key = $this->entityType->getKey('id'); + $revision_key = $this->entityType->getKey('revision'); + return array( + 'description' => "The revision data table for {$this->entityType->id()} entities.", + 'primary key' => array($revision_key, $this->entityType->getKey('language')), + 'foreign keys' => array( + $id_key => array( + 'table' => $this->entityType->getBaseTable(), + 'columns' => array($id_key => $id_key), + ), + $revision_key => array( + 'table' => $this->entityType->getRevisionTable(), + 'columns' => array($revision_key => $revision_key), + ) + ), + ); + } + + /** + * Processes the gathered schema for a base table. + * + * @param array $schema + * The table schema, passed by reference. + * + * @return array + * A partial schema array for the base table. + */ + protected function processBaseTable(array &$schema) { + $id_key = $this->entityType->getKey('id'); + // Change the ID field in the base table to 'serial' if it is 'int'. + if ($schema['fields'][$id_key]['type'] == 'int') { + $schema['fields'][$id_key]['type'] = 'serial'; + unset($schema['fields'][$id_key]['default']); + } + } + + /** + * Processes the gathered schema for a base table. + * + * @param array $schema + * The table schema, passed by reference. + * @param array $entity_keys + * An array of relevant entity keys. + * + * @return array + * A partial schema array for the base table. + */ + protected function processRevisionTable(array &$schema) { + $revision_key = $this->entityType->getKey('revision'); + // Change the revision ID field in the revision table 'serial' if it is + // 'int'. + if ($schema['fields'][$revision_key]['type'] == 'int') { + $schema['fields'][$revision_key]['type'] = 'serial'; + unset($schema['fields'][$revision_key]['default']); + } + } + + /** + * Processes the gathered schema for a base table. + * + * @param array $schema + * The table schema, passed by reference. + * + * @return array + * A partial schema array for the base table. + */ + protected function processDataTable(array &$schema) { + } + + /** + * Processes the gathered schema for a base table. + * + * @param array $schema + * The table schema, passed by reference. + * + * @return array + * A partial schema array for the base table. + */ + protected function processRevisionDataTable(array &$schema) { + } + +} diff --git a/core/lib/Drupal/Core/Entity/Schema/SchemaBuilderInterface.php b/core/lib/Drupal/Core/Entity/Schema/SchemaBuilderInterface.php new file mode 100644 index 0000000..7645871 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Schema/SchemaBuilderInterface.php @@ -0,0 +1,41 @@ +isNewRevision()) { // When inserting either a new custom block or a new custom_block @@ -130,14 +130,14 @@ public function preSaveRevision(EntityStorageControllerInterface $storage_contro // that it is at least an empty string in that case. // @todo: Make the {block_custom_revision}.log column nullable so that we // can remove this check. - if (!isset($record->log)) { - $record->log = ''; + if (!isset($values['log'])) { + $values['log'] = ''; } } - elseif (isset($this->original) && (!isset($record->log) || $record->log === '')) { + elseif (isset($this->original) && (!isset($values['log']) || $values['log'] === '')) { // If we are updating an existing custom_block without adding a new // revision and the user did not supply a log, keep the existing one. - $record->log = $this->original->getRevisionLog(); + $values['log'] = $this->original->getRevisionLog(); } } diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php index a502732..9ce01ff 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -9,6 +9,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\FieldableDatabaseStorageController; use Drupal\field\FieldInfo; @@ -36,13 +37,15 @@ class CommentStorageController extends FieldableDatabaseStorageController implem * An array of entity info for the entity type. * @param \Drupal\Core\Database\Connection $database * The database connection to be used. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. * @param \Drupal\field\FieldInfo $field_info * The field info service. * @param \Drupal\comment\CommentStatisticsInterface $comment_statistics * The comment statistics service. */ - public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, CommentStatisticsInterface $comment_statistics) { - parent::__construct($entity_info, $database, $field_info); + public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, EntityManagerInterface $entity_manager, CommentStatisticsInterface $comment_statistics) { + parent::__construct($entity_info, $database, $field_info, $entity_manager); $this->statistics = $comment_statistics; } @@ -54,6 +57,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $entity_info, $container->get('database'), $container->get('field.info'), + $container->get('entity.manager'), $container->get('comment.statistics') ); } diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module index cc22891..558a85e 100644 --- a/core/modules/entity/entity.module +++ b/core/modules/entity/entity.module @@ -9,6 +9,7 @@ */ use Drupal\Core\Config\Entity\ConfigStorageController; +use Drupal\Core\Entity\SqlStorageControllerInterface; /** * Implements hook_help(). @@ -129,16 +130,55 @@ function entity_entity_bundle_delete($entity_type_id, $bundle) { } /** + * Implements hook_module_preinstall(). + */ +function entity_module_preinstall($module) { + // Install entity type tables. + $entity_manager = \Drupal::entityManager(); + $schema = \Drupal::database()->schema(); + + foreach ($entity_manager->getDefinitions() as $entity_type) { + if ($entity_type->getProvider() == $module) { + $storage_controller = $entity_manager->getStorageController($entity_type->id()); + if ($storage_controller instanceof SqlStorageControllerInterface) { + foreach ($storage_controller->getSchema() as $table_name => $table_schema) { + // @todo Remove this check once all entity types have been converted + // to an automatic schema. + if (!drupal_get_schema($table_name)) { + $schema->createTable($table_name, $table_schema); + } + } + } + } + } +} + +/** * Implements hook_module_preuninstall(). */ function entity_module_preuninstall($module) { - // Clean up all entity bundles (including field instances) of every entity - // type provided by the module that is being uninstalled. - foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) { + $entity_manager = \Drupal::entityManager(); + $schema = \Drupal::database()->schema(); + + foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->getProvider() == $module) { - foreach (array_keys(entity_get_bundles($entity_type_id)) as $bundle) { + // Clean up all entity bundles (including field instances) of every entity + // type provided by the module that is being uninstalled. + foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) { entity_invoke_bundle_hook('delete', $entity_type_id, $bundle); } + + // Remove entity tables. + $storage_controller = $entity_manager->getStorageController($entity_type->id()); + if ($storage_controller instanceof SqlStorageControllerInterface) { + foreach ($storage_controller->getSchema() as $table_name => $table_schema) { + // @todo Remove this check once all entity types have been converted + // to an automatic schema. + if ($schema->tableExists($table_name)) { + $schema->dropTable($table_name, $table_schema); + } + } + } } } } diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php index 4c971c6..0d678be 100644 --- a/core/modules/node/lib/Drupal/node/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php @@ -78,8 +78,8 @@ public function getRevisionId() { /** * {@inheritdoc} */ - public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { - parent::preSaveRevision($storage_controller, $record); + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, array $values) { + parent::preSaveRevision($storage_controller, $values); if ($this->newRevision) { // When inserting either a new node or a new node revision, $node->log @@ -90,16 +90,16 @@ public function preSaveRevision(EntityStorageControllerInterface $storage_contro // an empty string in that case. // @todo Make the {node_field_revision}.log column nullable so that we // can remove this check. - if (!isset($record->log)) { - $record->log = ''; + if (!isset($values['log'])) { + $values['log'] = ''; } } - elseif (isset($this->original) && (!isset($record->log) || $record->log === '')) { + elseif (isset($this->original) && (!isset($values['log']) || $values['log'] === '')) { // If we are updating an existing node without adding a new revision, we // need to make sure $entity->log is reset whenever it is empty. // Therefore, this code allows us to avoid clobbering an existing log // entry with an empty one. - $record->log = $this->original->log->value; + $values['log'] = $this->original->log->value; } } diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Entity/Shortcut.php b/core/modules/shortcut/lib/Drupal/shortcut/Entity/Shortcut.php index 56d4839..fc0a453 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Entity/Shortcut.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Entity/Shortcut.php @@ -95,8 +95,7 @@ public function setRouteName($route_name) { * {@inheritdoc} */ public function getRouteParams() { - $value = $this->get('route_parameters')->getValue(); - return reset($value); + return $this->get('route_parameters')->getValue(); } /** @@ -170,10 +169,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('Language code')) ->setDescription(t('The language code of the shortcut.')); - $fields['default_langcode'] = FieldDefinition::create('boolean') - ->setLabel(t('Default language')) - ->setDescription(t('Flag to indicate whether this is the default language.')); - $fields['path'] = FieldDefinition::create('string') ->setLabel(t('Path')) ->setDescription(t('The computed shortcut path.')) diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index 934c8b2..594ac8e 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -7,8 +7,10 @@ namespace Drupal\simpletest; +use Drupal\Component\Utility\String; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DrupalKernel; +use Drupal\Core\Entity\SqlStorageControllerInterface; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; use Drupal\Core\Language\Language; use Symfony\Component\DependencyInjection\Reference; @@ -335,6 +337,36 @@ protected function installSchema($module, $tables) { } /** + * Installs the tables for a specific entity type. + * + * @param string $entity_type_id + * The ID of the entity type. + */ + protected function installEntitySchema($entity_type_id) { + /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ + $entity_manager = $this->container->get('entity.manager'); + /** @var \Drupal\Core\Database\Schema $schema_handler */ + $schema_handler = $this->container->get('database')->schema(); + + $storage_controller = $entity_manager->getStorageController($entity_type_id); + if ($storage_controller instanceof SqlStorageControllerInterface) { + $schema = $storage_controller->getSchema(); + foreach ($schema as $table_name => $table_schema) { + $schema_handler->createTable($table_name, $table_schema); + } + $this->pass(String::format('Installed entity type tables for the %entity_type entity type: %tables', array( + '%entity_type' => $entity_type_id, + '%tables' => '{' . implode('}, {', array_keys($schema)) . '}', + ))); + } + else { + throw new \RuntimeException(String::format('Entity type %entity_type does not support automatic schema installation.', array( + '%entity-type' => $entity_type_id, + ))); + } + } + + /** * Enables modules for this test. * * @param array $modules diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php index a9f0105..5d2364c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php @@ -25,16 +25,10 @@ public static function getInfo() { public function setUp() { parent::setUp(); - $this->installSchema('entity_test', array( - 'entity_test_mul', - 'entity_test_mul_property_data', - 'entity_test_rev', - 'entity_test_rev_revision', - 'entity_test_mulrev', - 'entity_test_mulrev_revision', - 'entity_test_mulrev_property_data', - 'entity_test_mulrev_property_revision' - )); + + $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mulrev'); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php index 61c8c87..7435100 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php @@ -42,16 +42,10 @@ public function setUp() { parent::setUp(); $this->installSchema('user', array('users_data')); $this->installSchema('node', array('node', 'node_revision', 'node_field_data', 'node_field_revision', 'node_access')); - $this->installSchema('entity_test', array( - 'entity_test_mul', - 'entity_test_mul_property_data', - 'entity_test_rev', - 'entity_test_rev_revision', - 'entity_test_mulrev', - 'entity_test_mulrev_revision', - 'entity_test_mulrev_property_data', - 'entity_test_mulrev_property_revision' - )); + + $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mulrev'); // Create the test field. entity_test_install(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php index bd2ceb9..db45c92 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php @@ -50,16 +50,10 @@ function setUp() { $this->languageManager = $this->container->get('language_manager'); - $this->installSchema('entity_test', array( - 'entity_test_mul', - 'entity_test_mul_property_data', - 'entity_test_rev', - 'entity_test_rev_revision', - 'entity_test_mulrev', - 'entity_test_mulrev_revision', - 'entity_test_mulrev_property_data', - 'entity_test_mulrev_property_revision', - )); + $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mulrev'); + $this->installConfig(array('language')); // Create the test field. diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php index c01a2e2..31e16d0 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php @@ -57,7 +57,9 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->installSchema('entity_test', array('entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision')); + + $this->installEntitySchema('entity_test_mulrev'); + $this->installConfig(array('language')); $figures = drupal_strtolower($this->randomName()); diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUUIDTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUUIDTest.php index ec7134c..a0ec726 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUUIDTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUUIDTest.php @@ -23,16 +23,9 @@ public static function getInfo() { public function setUp() { parent::setUp(); - $this->installSchema('entity_test', array( - 'entity_test_mul', - 'entity_test_mul_property_data', - 'entity_test_rev', - 'entity_test_rev_revision', - 'entity_test_mulrev', - 'entity_test_mulrev_revision', - 'entity_test_mulrev_property_data', - 'entity_test_mulrev_property_revision', - )); + $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mulrev'); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php index 8537fbb..15f2bed 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php @@ -44,7 +44,9 @@ public function setUp() { $this->installSchema('user', array('users', 'users_roles')); $this->installSchema('system', 'sequences'); - $this->installSchema('entity_test', 'entity_test'); + + $this->installEntitySchema('entity_test'); + $this->installConfig(array('field')); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php index 118abe9..169ab3d 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php @@ -38,16 +38,10 @@ public static function getInfo() { public function setUp() { parent::setUp(); $this->installSchema('user', array('users_data')); - $this->installSchema('entity_test', array( - 'entity_test_mul', - 'entity_test_mul_property_data', - 'entity_test_rev', - 'entity_test_rev_revision', - 'entity_test_mulrev', - 'entity_test_mulrev_revision', - 'entity_test_mulrev_property_data', - 'entity_test_mulrev_property_revision' - )); + + $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mulrev'); // Create the test field. entity_test_install(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldSqlStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldSqlStorageTest.php index 8894088..aeb6542 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldSqlStorageTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldSqlStorageTest.php @@ -66,8 +66,8 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->installSchema('entity_test', array('entity_test_rev', 'entity_test_rev_revision')); - $entity_type = 'entity_test_rev'; + + $this->installEntitySchema('entity_test_rev'); $this->field_name = strtolower($this->randomName()); $this->field_cardinality = 4; diff --git a/core/modules/system/tests/modules/entity_test/entity_test.install b/core/modules/system/tests/modules/entity_test/entity_test.install index 9e48029..a105f95 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.install +++ b/core/modules/system/tests/modules/entity_test/entity_test.install @@ -9,433 +9,36 @@ * Implements hook_install(). */ function entity_test_install() { - $entity_types = array( + $entity_manager = \Drupal::entityManager(); + + $entity_type_ids = array( 'entity_test', 'entity_test_rev', 'entity_test_mul', 'entity_test_mulrev', ); - foreach ($entity_types as $entity_type) { + foreach ($entity_type_ids as $entity_type_id) { + $storage_controller = $entity_manager->getStorageController($entity_type_id); + // Auto-create fields for testing. - entity_create('field_config', array( + $storage_controller->create(array( 'name' => 'field_test_text', - 'entity_type' => $entity_type, + 'entity_type' => $entity_type_id, + 'user_id' => 1, 'type' => 'text', 'cardinality' => 1, 'translatable' => FALSE, ))->save(); - entity_create('field_instance_config', array( - 'entity_type' => $entity_type, + $storage_controller->create(array( + 'entity_type' => $entity_type_id, + 'user_id' => 1, 'field_name' => 'field_test_text', - 'bundle' => $entity_type, + 'bundle' => $entity_type_id, 'label' => 'Test text-field', ))->save(); - entity_get_form_display($entity_type, $entity_type, 'default') + entity_get_form_display($entity_type_id, $entity_type_id, 'default') ->setComponent('field_test_text', array('type' => 'text_textfield')) ->save(); } } - -/** - * Implements hook_schema(). - */ -function entity_test_schema() { - // Schema for simple entity. - $schema['entity_test'] = array( - 'description' => 'Stores entity_test items.', - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique entity-test item ID.', - ), - 'uuid' => array( - 'description' => 'Unique Key: Universally unique identifier for this entity.', - 'type' => 'varchar', - 'length' => 128, - 'not null' => FALSE, - ), - 'type' => array( - 'description' => 'The bundle of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of the original variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - 'name' => array( - 'description' => 'The name of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'user_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - 'default' => NULL, - 'description' => 'The {users}.uid of the associated user.', - ), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'uuid' => array('uuid'), - ), - ); - - // Schema for entity with revisions. - $schema['entity_test_rev'] = array( - 'description' => 'Stores entity_test_rev items.', - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique entity-test item ID.', - ), - 'revision_id' => array( - 'description' => 'The current {entity_test_rev_property_revision}.revision_id version identifier.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'uuid' => array( - 'description' => 'Unique Key: Universally unique identifier for this entity.', - 'type' => 'varchar', - 'length' => 128, - 'not null' => FALSE, - ), - 'type' => array( - 'description' => 'The bundle of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'name' => array( - 'description' => 'The name of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'user_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - 'default' => NULL, - 'description' => 'The {users}.uid of the associated user.', - ), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'uuid' => array('uuid'), - ), - ); - $schema['entity_test_rev_revision'] = array( - 'description' => 'Stores entity_test_rev item property revisions.', - 'fields' => array( - 'id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The {entity_test_rev}.id of the test entity.', - ), - 'revision_id' => array( - 'type' => 'serial', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The primary identifier for this version.', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of this variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - 'name' => array( - 'description' => 'The name of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'user_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - 'default' => NULL, - 'description' => 'The {users}.uid of the associated user.', - ), - ), - 'indexes' => array( - 'user_id' => array('user_id'), - ), - 'foreign keys' => array( - 'user_id' => array('users' => 'uid'), - 'id' => array('entity_test_rev' => 'id'), - ), - 'primary key' => array('revision_id'), - ); - - // Schema for entity with data table. - $schema['entity_test_mul'] = array( - 'description' => 'Stores entity_test_mul items.', - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique entity-test item ID.', - ), - 'uuid' => array( - 'description' => 'Unique Key: Universally unique identifier for this entity.', - 'type' => 'varchar', - 'length' => 128, - 'not null' => FALSE, - ), - 'type' => array( - 'description' => 'The bundle of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of the original variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'uuid' => array('uuid'), - ), - ); - $schema['entity_test_mul_property_data'] = array( - 'description' => 'Stores entity_test_mul item properties.', - 'fields' => array( - 'id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The {entity_test_mul}.id of the test entity.', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of this variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - 'default_langcode' => array( - 'description' => 'Boolean indicating whether the current variant is in the original entity language.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 1, - ), - 'name' => array( - 'description' => 'The name of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => FALSE, - ), - 'user_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - 'default' => NULL, - 'description' => 'The {users}.uid of the associated user.', - ), - ), - 'indexes' => array( - 'user_id' => array('user_id'), - ), - 'foreign keys' => array( - 'user_id' => array('users' => 'uid'), - 'id' => array('entity_test_mul' => 'id'), - ), - 'primary key' => array('id', 'langcode'), - ); - - // Schema for entity with data table and revisions. - $schema['entity_test_mulrev'] = array( - 'description' => 'Stores entity_test_mulrev items.', - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique entity-test item ID.', - ), - 'revision_id' => array( - 'description' => 'The current {entity_test_mulrev_property_revision}.revision_id version identifier.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'uuid' => array( - 'description' => 'Unique Key: Universally unique identifier for this entity.', - 'type' => 'varchar', - 'length' => 128, - 'not null' => FALSE, - ), - 'type' => array( - 'description' => 'The bundle of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'uuid' => array('uuid'), - ), - ); - $schema['entity_test_mulrev_revision'] = array( - 'description' => 'Stores entity_test_rev item property revisions.', - 'fields' => array( - 'id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The {entity_test_rev}.id of the test entity.', - ), - 'revision_id' => array( - 'type' => 'serial', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The primary identifier for this version.', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of this variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - ), - 'foreign keys' => array( - 'id' => array('entity_test_rev' => 'id'), - ), - 'primary key' => array('revision_id'), - ); - $schema['entity_test_mulrev_property_data'] = array( - 'description' => 'Stores entity_test_mulrev item properties.', - 'fields' => array( - 'id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The {entity_test_mulrev}.id of the test entity.', - ), - 'revision_id' => array( - 'description' => 'The current {entity_test_mulrev_property_revision}.revision_id version identifier.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of this variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - 'default_langcode' => array( - 'description' => 'Boolean indicating whether the current variant is in the original entity language.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 1, - ), - 'name' => array( - 'description' => 'The name of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'user_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - 'default' => NULL, - 'description' => 'The {users}.uid of the associated user.', - ), - ), - 'indexes' => array( - 'user_id' => array('user_id'), - ), - 'foreign keys' => array( - 'user_id' => array('users' => 'uid'), - 'id' => array('entity_test_mulrev' => 'id'), - ), - 'primary key' => array('id', 'langcode'), - ); - $schema['entity_test_mulrev_property_revision'] = array( - 'description' => 'Stores entity_test_mulrev item property revisions.', - 'fields' => array( - 'id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The {entity_test_mulrev}.id of the test entity.', - ), - 'revision_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The primary identifier for this version.', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of this variant of this test entity.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - 'default_langcode' => array( - 'description' => 'Boolean indicating whether the current variant is in the original entity language.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 1, - ), - 'name' => array( - 'description' => 'The name of the test entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'user_id' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - 'default' => NULL, - 'description' => 'The {users}.uid of the associated user.', - ), - ), - 'indexes' => array( - 'user_id' => array('user_id'), - ), - 'foreign keys' => array( - 'user_id' => array('users' => 'uid'), - 'id' => array('entity_test_mulrev' => 'id'), - ), - 'primary key' => array('revision_id', 'langcode'), - ); - - return $schema; -} diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php index 1f93df1..06dd4d3 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php @@ -86,7 +86,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['type'] = FieldDefinition::create('string') ->setLabel(t('Type')) ->setDescription(t('The bundle of the test entity.')) - ->setRequired(TRUE); + ->setRequired(TRUE) + ->setSetting('max_length', 32); $fields['user_id'] = FieldDefinition::create('entity_reference') ->setLabel(t('User ID')) @@ -94,6 +95,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setSettings(array('target_type' => 'user')) ->setTranslatable(TRUE); + $fields['field_test_text'] = FieldDefinition::create('text') + ->setLabel(t('Test text')) + ->setDescription(t('A test text field')); + return $fields; } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestBaseFieldDisplay.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestBaseFieldDisplay.php index c76cff7..6b7100a 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestBaseFieldDisplay.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestBaseFieldDisplay.php @@ -28,7 +28,6 @@ * entity_keys = { * "id" = "id", * "uuid" = "uuid", - * "revision" = "revision_id", * "bundle" = "type" * }, * links = { diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php index 481711f..e881b9a 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php @@ -15,7 +15,6 @@ * label = @Translation("Entity test label callback"), * field_cache = FALSE, * base_table = "entity_test", - * revision_table = "entity_test_revision", * label_callback = "entity_test_label_callback", * fieldable = TRUE, * entity_keys = { diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php index 49f7d5f..bec368c 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php @@ -44,17 +44,4 @@ */ class EntityTestMul extends EntityTest { - /** - * {@inheritdoc} - */ - public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { - $fields = parent::baseFieldDefinitions($entity_type); - - $fields['default_langcode'] = FieldDefinition::create('boolean') - ->setLabel(t('Default language')) - ->setDescription(t('Flag to indicate whether this is the default language.')); - - return $fields; - } - } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php index 870af93..1b979bb 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php @@ -55,10 +55,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDescription(t('The version id of the test entity.')) ->setReadOnly(TRUE); - $fields['default_langcode'] = FieldDefinition::create('boolean') - ->setLabel(t('Default language')) - ->setDescription(t('Flag to indicate whether this is the default language.')); - return $fields; } diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php index 0694019..81f776e 100644 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ b/core/modules/user/lib/Drupal/user/UserStorageController.php @@ -9,7 +9,9 @@ use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\Schema\SchemaBuilderInterface; use Drupal\Core\Password\PasswordInterface; use Drupal\Core\Database\Connection; use Drupal\field\FieldInfo; @@ -48,15 +50,17 @@ class UserStorageController extends FieldableDatabaseStorageController implement * The database connection to be used. * @param \Drupal\field\FieldInfo $field_info * The field info service. - * @param \Drupal\Component\Uuid\UuidInterface $uuid_service - * The UUID Service. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. + * @param \Drupal\Core\Entity\Schema\SchemaBuilderInterface $schema_builder + * The entity schema builder. * @param \Drupal\Core\Password\PasswordInterface $password * The password hashing service. * @param \Drupal\user\UserDataInterface $user_data * The user data service. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, FieldInfo $field_info, UuidInterface $uuid_service, PasswordInterface $password, UserDataInterface $user_data) { - parent::__construct($entity_type, $database, $field_info, $uuid_service); + public function __construct(EntityTypeInterface $entity_type, Connection $database, FieldInfo $field_info, EntityManagerInterface $entity_manager, PasswordInterface $password, UserDataInterface $user_data) { + parent::__construct($entity_type, $database, $field_info, $entity_manager); $this->password = $password; $this->userData = $user_data; @@ -70,7 +74,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $entity_type, $container->get('database'), $container->get('field.info'), - $container->get('uuid'), + $container->get('entity.manager'), $container->get('password'), $container->get('user.data') );