/** * Gets the field storage definitions. * * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[] */ protected function getFieldStorageDefinitions() { if (!isset($this->fieldStorageDefinitions)) { $this->fieldStorageDefinitions = $this->entityManager->getFieldStorageDefinitions($this->entityType->id()); } return $this->fieldStorageDefinitions; }
/** * Retrieves suggestions for taxonomy term autocompletion. * * This function outputs term name suggestions in response to Ajax requests * made by the taxonomy autocomplete widget for taxonomy term reference * fields. The output is a JSON object of plain-text term suggestions, keyed * by the user-entered value with the completed term name appended. * Term names containing commas are wrapped in quotes. * * For example, suppose the user has entered the string 'red fish, blue' in * the field, and there are two taxonomy terms, 'blue fish' and 'blue moon'. * The JSON output would have the following structure: * @code * { * "red fish, blue fish": "blue fish", * "red fish, blue moon": "blue moon", * }; * @endcode * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. * @param string $entity_type * The entity_type. * @param string $field_name * The name of the term reference field. * * @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\Response * When valid field name is specified, a JSON response containing the * autocomplete suggestions for taxonomy terms. Otherwise a normal response * containing an error message. */ public function autocomplete(Request $request, $entity_type, $field_name) { // A comma-separated list of term names entered in the autocomplete form // element. Only the last term is used for autocompletion. $tags_typed = $request->query->get('q'); // Make sure the field exists and is a taxonomy field. $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type); if (!isset($field_storage_definitions[$field_name]) || $field_storage_definitions[$field_name]->getType() !== 'taxonomy_term_reference') { // Error string. The JavaScript handler will realize this is not JSON and // will display it as debugging information. return new Response(t('Taxonomy field @field_name not found.', array('@field_name' => $field_name)), 403); } $field_storage = $field_storage_definitions[$field_name]; // The user enters a comma-separated list of tags. We only autocomplete the // last tag. $tags_typed = Tags::explode($tags_typed); $tag_last = Unicode::strtolower(array_pop($tags_typed)); $matches = array(); if ($tag_last != '') { // Part of the criteria for the query come from the field's own settings. $vids = array(); foreach ($field_storage->getSetting('allowed_values') as $tree) { $vids[] = $tree['vocabulary']; } $matches = $this->getMatchingTerms($tags_typed, $vids, $tag_last); } return new JsonResponse($matches); }
/** * Gets the entity schema for the specified entity type. * * Entity types may override this method in order to optimize the generated * schema of the entity tables. However, only cross-field optimizations should * be added here; e.g., an index spanning multiple fields. Optimizations that * apply to a single field have to be added via * SqlContentEntityStorageSchema::getSharedTableFieldSchema() instead. * * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type * The entity type definition. * @param bool $reset * (optional) If set to TRUE static cache will be ignored and a new schema * array generation will be performed. Defaults to FALSE. * * @return array * A Schema API array describing the entity schema, excluding dedicated * field tables. * * @throws \Drupal\Core\Field\FieldException */ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) { $this->checkEntityType($entity_type); $entity_type_id = $entity_type->id(); if (!isset($this->schema[$entity_type_id]) || $reset) { // Back up the storage definition and replace it with the passed one. // @todo Instead of switching the wrapped entity type, we should be able // to instantiate a new table mapping for each entity type definition. // See https://www.drupal.org/node/2274017. $actual_definition = $this->entityManager->getDefinition($entity_type_id); $this->storage->setEntityType($entity_type); // Prepare basic information about the entity type. $tables = $this->getEntitySchemaTables(); // Initialize the table schema. $schema[$tables['base_table']] = $this->initializeBaseTable($entity_type); if (isset($tables['revision_table'])) { $schema[$tables['revision_table']] = $this->initializeRevisionTable($entity_type); } if (isset($tables['data_table'])) { $schema[$tables['data_table']] = $this->initializeDataTable($entity_type); } if (isset($tables['revision_data_table'])) { $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable($entity_type); } // We need to act only on shared entity schema tables. $table_mapping = $this->storage->getTableMapping(); $table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()); $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); foreach ($table_names as $table_name) { if (!isset($schema[$table_name])) { $schema[$table_name] = array(); } foreach ($table_mapping->getFieldNames($table_name) as $field_name) { if (!isset($storage_definitions[$field_name])) { throw new FieldException(SafeMarkup::format('Field storage definition for "@field_name" could not be found.', array('@field_name' => $field_name))); } elseif ($table_mapping->allowsSharedTableStorage($storage_definitions[$field_name])) { $column_names = $table_mapping->getColumnNames($field_name); $storage_definition = $storage_definitions[$field_name]; $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names)); } } } // Process tables after having gathered field information. $this->processBaseTable($entity_type, $schema[$tables['base_table']]); if (isset($tables['revision_table'])) { $this->processRevisionTable($entity_type, $schema[$tables['revision_table']]); } if (isset($tables['data_table'])) { $this->processDataTable($entity_type, $schema[$tables['data_table']]); } if (isset($tables['revision_data_table'])) { $this->processRevisionDataTable($entity_type, $schema[$tables['revision_data_table']]); } $this->schema[$entity_type_id] = $schema; // Restore the actual definition. $this->storage->setEntityType($actual_definition); } return $this->schema[$entity_type_id]; }
/** * Gets the type of the ID key for a given entity type. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * An entity type. * * @return string|null * The type of the ID key for a given entity type, or NULL if the entity * type does not support fields. */ protected function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) { if (!$entity_type->isSubclassOf(FieldableEntityInterface::class)) { return NULL; } $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type->id()); return $field_storage_definitions[$entity_type->getKey('id')]->getType(); }
/** * Executes field storage definition updates if needed. * * @param array $entity_types * A list of entity type definitions to be processed. */ public function updateDefinitions(array $entity_types) { // Handle field storage definition creation, if needed. // @todo Generalize this code in https://www.drupal.org/node/2346013. // @todo Handle initial values in https://www.drupal.org/node/2346019. if ($this->updateManager->needsUpdates()) { foreach ($entity_types as $entity_type_id => $entity_type) { $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); $installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id); foreach (array_diff_key($storage_definitions, $installed_storage_definitions) as $storage_definition) { /** @var $storage_definition \Drupal\Core\Field\FieldStorageDefinitionInterface */ if ($storage_definition->getProvider() == 'content_translation') { $this->updateManager->installFieldStorageDefinition($storage_definition->getName(), $entity_type_id, 'content_translation', $storage_definition); } } } } }
/** * Gets a list of changes to entity type and field storage definitions. * * @return array * An associative array keyed by entity type id of change descriptors. Every * entry is an associative array with the following optional keys: * - entity_type: a scalar having only the DEFINITION_UPDATED value. * - field_storage_definitions: an associative array keyed by field name of * scalars having one value among: * - DEFINITION_CREATED * - DEFINITION_UPDATED * - DEFINITION_DELETED */ protected function getChangeList() { $this->entityManager->useCaches(FALSE); $change_list = array(); foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { $original = $this->entityManager->getLastInstalledDefinition($entity_type_id); // @todo Support non-storage-schema-changing definition updates too: // https://www.drupal.org/node/2336895. if (!$original) { $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED; } else { if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) { $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED; } if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) { $field_changes = array(); $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id); // Detect created field storage definitions. foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) { $field_changes[$field_name] = static::DEFINITION_CREATED; } // Detect deleted field storage definitions. foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) { $field_changes[$field_name] = static::DEFINITION_DELETED; } // Detect updated field storage definitions. foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) { // @todo Support non-storage-schema-changing definition updates too: // https://www.drupal.org/node/2336895. So long as we're checking // based on schema change requirements rather than definition // equality, skip the check if the entity type itself needs to be // updated, since that can affect the schema of all fields, so we // want to process that update first without reporting false // positives here. if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) { $field_changes[$field_name] = static::DEFINITION_UPDATED; } } if ($field_changes) { $change_list[$entity_type_id]['field_storage_definitions'] = $field_changes; } } } } // @todo Support deleting entity definitions when we support base field // purging. See https://www.drupal.org/node/2282119. $this->entityManager->useCaches(TRUE); return array_filter($change_list); }
/** * Gets the field storage of the used field. * * @return \Drupal\Core\Field\FieldStorageDefinitionInterface */ protected function getFieldStorageDefinition() { $entity_type_id = $this->definition['entity_type']; $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); $field_storage = NULL; // @todo Unify 'entity field'/'field_name' instead of converting back and // forth. https://www.drupal.org/node/2410779 if (isset($this->definition['field_name'])) { $field_storage = $field_storage_definitions[$this->definition['field_name']]; } elseif (isset($this->definition['entity field'])) { $field_storage = $field_storage_definitions[$this->definition['entity field']]; } return $field_storage; }
/** * Gets entity schema definitions for index and key definitions. * * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type * The entity type definition. * @param array $schema * The entity schema array. * * @return array * A stripped down version of the $schema Schema API array containing, for * each table, only the key and index definitions not derived from field * storage definitions. */ protected function getEntitySchemaData(ContentEntityTypeInterface $entity_type, array $schema) { $entity_type_id = $entity_type->id(); // Collect all possible field schema identifiers for shared table fields. // These will be used to detect entity schema data in the subsequent loop. $field_schema_identifiers = []; $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); $table_mapping = $this->storage->getTableMapping($storage_definitions); foreach ($storage_definitions as $field_name => $storage_definition) { if ($table_mapping->allowsSharedTableStorage($storage_definition)) { // Make sure both base identifier names and suffixed names are listed. $name = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name); $field_schema_identifiers[$name] = $name; foreach ($storage_definition->getColumns() as $key => $columns) { $name = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name, $key); $field_schema_identifiers[$name] = $name; } } } // Extract entity schema data from the Schema API definition. $schema_data = []; $keys = ['indexes', 'unique keys']; $unused_keys = array_flip(['description', 'fields', 'foreign keys']); foreach ($schema as $table_name => $table_schema) { $table_schema = array_diff_key($table_schema, $unused_keys); foreach ($keys as $key) { // Exclude data generated from field storage definitions, we will check // that separately. if ($field_schema_identifiers && !empty($table_schema[$key])) { $table_schema[$key] = array_diff_key($table_schema[$key], $field_schema_identifiers); } } $schema_data[$table_name] = array_filter($table_schema); } return $schema_data; }
/** * Returns whether the passed field has been already deleted. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition * The field storage definition. * * @return bool * Whether the field has been already deleted. */ protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) { return !array_key_exists($storage_definition->getName(), $this->entityManager->getFieldStorageDefinitions($this->entityTypeId)); }
/** * {@inheritdoc} */ public function getTableMapping(array $storage_definitions = NULL) { $table_mapping = $this->tableMapping; // If we are using our internal storage definitions, which is our main use // case, we can statically cache the computed table mapping. If a new set // of field storage definitions is passed, for instance when comparing old // and new storage schema, we compute the table mapping without caching. // @todo Clean-up this in https://www.drupal.org/node/2274017 so we can // easily instantiate a new table mapping whenever needed. if (!isset($this->tableMapping) || $storage_definitions) { $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId); $table_mapping = new DefaultTableMapping($this->entityType, $definitions); $definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use($table_mapping) { return $table_mapping->allowsSharedTableStorage($definition); }); $key_fields = array_values(array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey))); $all_fields = array_keys($definitions); $revisionable_fields = array_keys(array_filter($definitions, 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://www.drupal.org/node/2248983. $revision_metadata_fields = array_intersect(array('revision_timestamp', 'revision_uid', 'revision_log'), $all_fields); $revisionable = $this->entityType->isRevisionable(); $translatable = $this->entityType->isTranslatable(); if (!$revisionable && !$translatable) { // The base layout stores all the base field values in the base table. $table_mapping->setFieldNames($this->baseTable, $all_fields); } elseif ($revisionable && !$translatable) { // The revisionable layout stores all the base field values in the base // table, except for revision metadata fields. Revisionable fields // denormalized in the base table but also stored in the revision table // together with the entity ID and the revision ID as identifiers. $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields)); $revision_key_fields = array($this->idKey, $this->revisionKey); $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); } elseif (!$revisionable && $translatable) { // Multilingual layouts store key field values in the base table. The // other base field values are stored in the data table, no matter // whether they are translatable or not. The data table holds also a // denormalized copy of the bundle field value to allow for more // performant queries. This means that only the UUID is not stored on // the data table. $table_mapping->setFieldNames($this->baseTable, $key_fields)->setFieldNames($this->dataTable, array_values(array_diff($all_fields, array($this->uuidKey)))); } elseif ($revisionable && $translatable) { // The revisionable multilingual layout stores key field values in the // base table, except for language, which is stored in the revision // table along with revision metadata. The revision data table holds // data field values for all the revisionable fields and the data table // holds the data field values for all non-revisionable fields. The data // field values of revisionable fields are denormalized in the data // table, as well. $table_mapping->setFieldNames($this->baseTable, array_values($key_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_values(array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields)); $table_mapping->setFieldNames($this->dataTable, $data_fields); $revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields); $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields); $revision_data_key_fields = array($this->idKey, $this->revisionKey, $this->langcodeKey); $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, array($this->langcodeKey)); $table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields)); } // Add dedicated tables. $definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use($table_mapping) { return $table_mapping->requiresDedicatedTableStorage($definition); }); $extra_columns = array('bundle', 'deleted', 'entity_id', 'revision_id', 'langcode', 'delta'); foreach ($definitions as $field_name => $definition) { foreach (array($table_mapping->getDedicatedDataTableName($definition), $table_mapping->getDedicatedRevisionTableName($definition)) as $table_name) { $table_mapping->setFieldNames($table_name, array($field_name)); $table_mapping->setExtraColumns($table_name, $extra_columns); } } // Cache the computed table mapping only if we are using our internal // storage definitions. if (!$storage_definitions) { $this->tableMapping = $table_mapping; } } return $table_mapping; }
/** * {@inheritdoc} */ public function getFieldStorageDefinitions($entity_type_id) { return $this->entityManager->getFieldStorageDefinitions($entity_type_id); }
protected function defineOptions() { $options = parent::defineOptions(); $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->definition['entity_type']); $field_storage = $field_storage_definitions[$this->definition['field_name']]; $field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field_storage->getType()); $column_names = array_keys($field_storage->getColumns()); $default_column = ''; // Try to determine a sensible default. if (count($column_names) == 1) { $default_column = $column_names[0]; } elseif (in_array('value', $column_names)) { $default_column = 'value'; } // If the field has a "value" column, we probably need that one. $options['click_sort_column'] = array('default' => $default_column); $options['type'] = array('default' => $field_type['default_formatter']); $options['settings'] = array('default' => array()); $options['group_column'] = array('default' => $default_column); $options['group_columns'] = array('default' => array()); // Options used for multiple value fields. $options['group_rows'] = array('default' => TRUE, 'bool' => TRUE); // If we know the exact number of allowed values, then that can be // the default. Otherwise, default to 'all'. $options['delta_limit'] = array('default' => $field_storage->getCardinality() > 1 ? $field_storage->getCardinality() : 'all'); $options['delta_offset'] = array('default' => 0); $options['delta_reversed'] = array('default' => FALSE, 'bool' => TRUE); $options['delta_first_last'] = array('default' => FALSE, 'bool' => TRUE); $options['multi_type'] = array('default' => 'separator'); $options['separator'] = array('default' => ', '); $options['field_api_classes'] = array('default' => FALSE, 'bool' => TRUE); return $options; }
/** * Constructs a ContentEntitySchemaHandler. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type * The entity type. * @param \Drupal\Core\Entity\ContentEntityDatabaseStorage $storage * The storage of the entity type. This must be an SQL-based storage. */ public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorage $storage) { $this->entityType = $entity_type; $this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id()); $this->storage = $storage; }