Issue #1497374 by yched, chx, amateescu, plach, Damien Tournoud, fago, swentel: Switch from Field-based storage to Entity-based storage.

8.0.x
Nathaniel Catchpole 2013-09-01 07:20:08 +01:00
parent df5b3bc501
commit c7994001cc
215 changed files with 3500 additions and 4737 deletions

View File

@ -102,6 +102,12 @@ function entity_get_bundles($entity_type = NULL) {
*/
function entity_invoke_bundle_hook($hook, $entity_type, $bundle, $bundle_new = NULL) {
entity_info_cache_clear();
// Notify the entity storage controller.
$method = 'onBundle' . ucfirst($hook);
Drupal::entityManager()->getStorageController($entity_type)->$method($bundle, $bundle_new);
// Invoke hook_entity_bundle_*() hooks.
Drupal::moduleHandler()->invokeAll('entity_bundle_' . $hook, array($entity_type, $bundle, $bundle_new));
}

View File

@ -75,4 +75,14 @@ interface ConfigEntityInterface extends EntityInterface {
*/
public function status();
/**
* Retrieves the exportable properties of the entity.
*
* These are the values that get saved into config.
*
* @return array
* An array of exportable properties and their values.
*/
public function getExportProperties();
}

View File

@ -7,13 +7,15 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Language\Language;
use PDO;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Uuid\Uuid;
use Drupal\field\FieldInfo;
use Drupal\field\FieldUpdateForbiddenException;
use Drupal\field\FieldInterface;
use Drupal\field\FieldInstanceInterface;
use Drupal\field\Entity\Field;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -24,7 +26,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* This class can be used as-is by most simple entity types. Entity types
* requiring special handling can extend the class.
*/
class DatabaseStorageController extends EntityStorageControllerBase {
class DatabaseStorageController extends FieldableEntityStorageControllerBase {
/**
* Name of entity's revision database table field, if it supports revisions.
@ -58,6 +60,13 @@ class DatabaseStorageController extends EntityStorageControllerBase {
*/
protected $database;
/**
* The field info object.
*
* @var \Drupal\field\FieldInfo
*/
protected $fieldInfo;
/**
* {@inheritdoc}
*/
@ -65,7 +74,8 @@ class DatabaseStorageController extends EntityStorageControllerBase {
return new static(
$entity_type,
$entity_info,
$container->get('database')
$container->get('database'),
$container->get('field.info')
);
}
@ -78,11 +88,14 @@ class DatabaseStorageController extends EntityStorageControllerBase {
* An array of entity info for the entity type.
* @param \Drupal\Core\Database\Connection $database
* The database connection to be used.
* @param \Drupal\field\FieldInfo $field_info
* The field info service.
*/
public function __construct($entity_type, array $entity_info, Connection $database) {
public function __construct($entity_type, array $entity_info, Connection $database, FieldInfo $field_info) {
parent::__construct($entity_type, $entity_info);
$this->database = $database;
$this->fieldInfo = $field_info;
// Check if the entity type supports IDs.
if (isset($this->entityInfo['entity_keys']['id'])) {
@ -143,7 +156,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
// We provide the necessary arguments for PDO to create objects of the
// specified entity class.
// @see Drupal\Core\Entity\EntityInterface::__construct()
$query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
$query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
}
$queried_entities = $query_result->fetchAllAssoc($this->idKey);
}
@ -196,7 +209,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
// We provide the necessary arguments for PDO to create objects of the
// specified entity class.
// @see Drupal\Core\Entity\EntityInterface::__construct()
$query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
$query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
}
$queried_entities = $query_result->fetchAllAssoc($this->idKey);
@ -223,6 +236,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
->condition($this->revisionKey, $revision->getRevisionId())
->execute();
$this->invokeFieldMethod('deleteRevision', $revision);
$this->deleteFieldItemsRevision($revision);
$this->invokeHook('revision_delete', $revision);
}
}
@ -335,14 +349,9 @@ class DatabaseStorageController extends EntityStorageControllerBase {
* (optional) TRUE if the revision should be loaded, defaults to FALSE.
*/
protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
// Attach fields.
// Attach field values.
if ($this->entityInfo['fieldable']) {
if ($load_revision) {
field_attach_load_revision($this->entityType, $queried_entities);
}
else {
field_attach_load($this->entityType, $queried_entities);
}
$this->loadFieldItems($queried_entities, $load_revision ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT);
}
// Call hook_entity_load().
@ -416,6 +425,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
$entity_class::postDelete($this, $entities);
foreach ($entities as $entity) {
$this->invokeFieldMethod('delete', $entity);
$this->deleteFieldItems($entity);
$this->invokeHook('delete', $entity);
}
// Ignore slave server temporarily.
@ -458,6 +468,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
$this->resetCache(array($entity->id()));
$entity->postSave($this, TRUE);
$this->invokeFieldMethod('update', $entity);
$this->saveFieldItems($entity, TRUE);
$this->invokeHook('update', $entity);
}
else {
@ -471,6 +482,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
$entity->enforceIsNew(FALSE);
$entity->postSave($this, FALSE);
$this->invokeFieldMethod('insert', $entity);
$this->saveFieldItems($entity, FALSE);
$this->invokeHook('insert', $entity);
}
@ -490,7 +502,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
/**
* Saves an entity revision.
*
* @param Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
protected function saveRevision(EntityInterface $entity) {
@ -528,34 +540,654 @@ class DatabaseStorageController extends EntityStorageControllerBase {
}
/**
* Invokes a hook on behalf of the entity.
*
* @param $hook
* One of 'presave', 'insert', 'update', 'predelete', 'delete', or
* 'revision_delete'.
* @param $entity
* The entity object.
*/
protected function invokeHook($hook, EntityInterface $entity) {
$function = 'field_attach_' . $hook;
// @todo: field_attach_delete_revision() is named the wrong way round,
// consider renaming it.
if ($function == 'field_attach_revision_delete') {
$function = 'field_attach_delete_revision';
}
if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
$function($entity);
}
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity-level hook.
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
}
/**
* Implements \Drupal\Core\Entity\EntityStorageControllerInterface::getQueryServiceName().
* {@inheritdoc}
*/
public function getQueryServiceName() {
return 'entity.query.sql';
}
/**
* {@inheritdoc}
*/
protected function doLoadFieldItems($entities, $age) {
$load_current = $age == FIELD_LOAD_CURRENT;
// Collect entities ids and bundles.
$bundles = array();
$ids = array();
foreach ($entities as $key => $entity) {
$bundles[$entity->bundle()] = TRUE;
$ids[] = $load_current ? $key : $entity->getRevisionId();
}
// Collect impacted fields.
$fields = array();
foreach ($bundles as $bundle => $v) {
foreach ($this->fieldInfo->getBundleInstances($this->entityType, $bundle) as $field_name => $instance) {
$fields[$field_name] = $instance->getField();
}
}
// Load field data.
foreach ($fields as $field_name => $field) {
$table = $load_current ? static::_fieldTableName($field) : static::_fieldRevisionTableName($field);
$results = $this->database->select($table, 't')
->fields('t')
->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
->condition('langcode', field_available_languages($this->entityType, $field), 'IN')
->orderBy('delta')
->condition('deleted', 0)
->execute();
$delta_count = array();
foreach ($results as $row) {
if (!isset($delta_count[$row->entity_id][$row->langcode])) {
$delta_count[$row->entity_id][$row->langcode] = 0;
}
if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field['cardinality']) {
$item = array();
// For each column declared by the field, populate the item from the
// prefixed database column.
foreach ($field['columns'] as $column => $attributes) {
$column_name = static::_fieldColumnName($field, $column);
// Unserialize the value if specified in the column schema.
$item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
}
// Add the item to the field values for the entity.
$entities[$row->entity_id]->{$field_name}[$row->langcode][] = $item;
$delta_count[$row->entity_id][$row->langcode]++;
}
}
}
}
/**
* {@inheritdoc}
*/
protected function doSaveFieldItems(EntityInterface $entity, $update) {
$vid = $entity->getRevisionId();
$id = $entity->id();
$bundle = $entity->bundle();
$entity_type = $entity->entityType();
if (!isset($vid)) {
$vid = $id;
}
foreach ($this->fieldInfo->getBundleInstances($entity_type, $bundle) as $field_name => $instance) {
$field = $instance->getField();
$table_name = static::_fieldTableName($field);
$revision_name = static::_fieldRevisionTableName($field);
$all_langcodes = field_available_languages($entity_type, $field);
$field_langcodes = array_intersect($all_langcodes, array_keys((array) $entity->$field_name));
// Delete and insert, rather than update, in case a value was added.
if ($update) {
// Delete language codes present in the incoming $entity->$field_name.
// Delete all language codes if $entity->$field_name is empty.
$langcodes = !empty($entity->$field_name) ? $field_langcodes : $all_langcodes;
if ($langcodes) {
// Only overwrite the field's base table if saving the default revision
// of an entity.
if ($entity->isDefaultRevision()) {
$this->database->delete($table_name)
->condition('entity_id', $id)
->condition('langcode', $langcodes, 'IN')
->execute();
}
$this->database->delete($revision_name)
->condition('entity_id', $id)
->condition('revision_id', $vid)
->condition('langcode', $langcodes, 'IN')
->execute();
}
}
// Prepare the multi-insert query.
$do_insert = FALSE;
$columns = array('entity_id', 'revision_id', 'bundle', 'delta', 'langcode');
foreach ($field['columns'] as $column => $attributes) {
$columns[] = static::_fieldColumnName($field, $column);
}
$query = $this->database->insert($table_name)->fields($columns);
$revision_query = $this->database->insert($revision_name)->fields($columns);
foreach ($field_langcodes as $langcode) {
$items = (array) $entity->{$field_name}[$langcode];
$delta_count = 0;
foreach ($items as $delta => $item) {
// We now know we have someting to insert.
$do_insert = TRUE;
$record = array(
'entity_id' => $id,
'revision_id' => $vid,
'bundle' => $bundle,
'delta' => $delta,
'langcode' => $langcode,
);
foreach ($field['columns'] as $column => $attributes) {
$column_name = static::_fieldColumnName($field, $column);
$value = isset($item[$column]) ? $item[$column] : NULL;
// Serialize the value if specified in the column schema.
$record[$column_name] = (!empty($attributes['serialize'])) ? serialize($value) : $value;
}
$query->values($record);
$revision_query->values($record);
if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
break;
}
}
}
// Execute the query if we have values to insert.
if ($do_insert) {
// Only overwrite the field's base table if saving the default revision
// of an entity.
if ($entity->isDefaultRevision()) {
$query->execute();
}
$revision_query->execute();
}
}
}
/**
* {@inheritdoc}
*/
protected function doDeleteFieldItems(EntityInterface $entity) {
foreach ($this->fieldInfo->getBundleInstances($entity->entityType(), $entity->bundle()) as $instance) {
$field = $instance->getField();
$table_name = static::_fieldTableName($field);
$revision_name = static::_fieldRevisionTableName($field);
$this->database->delete($table_name)
->condition('entity_id', $entity->id())
->execute();
$this->database->delete($revision_name)
->condition('entity_id', $entity->id())
->execute();
}
}
/**
* {@inheritdoc}
*/
protected function doDeleteFieldItemsRevision(EntityInterface $entity) {
$vid = $entity->getRevisionId();
if (isset($vid)) {
foreach ($this->fieldInfo->getBundleInstances($entity->entityType(), $entity->bundle()) as $instance) {
$revision_name = static::_fieldRevisionTableName($instance->getField());
$this->database->delete($revision_name)
->condition('entity_id', $entity->id())
->condition('revision_id', $vid)
->execute();
}
}
}
/**
* {@inheritdoc}
*/
public function onFieldCreate(FieldInterface $field) {
$schema = $this->_fieldSqlSchema($field);
foreach ($schema as $name => $table) {
$this->database->schema()->createTable($name, $table);
}
}
/**
* {@inheritdoc}
*/
public function onFieldUpdate(FieldInterface $field) {
$original = $field->original;
if (!$field->hasData()) {
// There is no data. Re-create the tables completely.
if ($this->database->supportsTransactionalDDL()) {
// If the database supports transactional DDL, we can go ahead and rely
// on it. If not, we will have to rollback manually if something fails.
$transaction = $this->database->startTransaction();
}
try {
$original_schema = $this->_fieldSqlSchema($original);
foreach ($original_schema as $name => $table) {
$this->database->schema()->dropTable($name, $table);
}
$schema = $this->_fieldSqlSchema($field);
foreach ($schema as $name => $table) {
$this->database->schema()->createTable($name, $table);
}
}
catch (\Exception $e) {
if ($this->database->supportsTransactionalDDL()) {
$transaction->rollback();
}
else {
// Recreate tables.
$original_schema = $this->_fieldSqlSchema($original);
foreach ($original_schema as $name => $table) {
if (!$this->database->schema()->tableExists($name)) {
$this->database->schema()->createTable($name, $table);
}
}
}
throw $e;
}
}
else {
if ($field['columns'] != $original['columns']) {
throw new FieldUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
}
// There is data, so there are no column changes. Drop all the prior
// indexes and create all the new ones, except for all the priors that
// exist unchanged.
$table = static::_fieldTableName($original);
$revision_table = static::_fieldRevisionTableName($original);
$schema = $field->getSchema();
$original_schema = $original->getSchema();
foreach ($original_schema['indexes'] as $name => $columns) {
if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
$real_name = static::_fieldIndexName($field, $name);
$this->database->schema()->dropIndex($table, $real_name);
$this->database->schema()->dropIndex($revision_table, $real_name);
}
}
$table = static::_fieldTableName($field);
$revision_table = static::_fieldRevisionTableName($field);
foreach ($schema['indexes'] as $name => $columns) {
if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) {
$real_name = static::_fieldIndexName($field, $name);
$real_columns = array();
foreach ($columns as $column_name) {
// Indexes can be specified as either a column name or an array with
// column name and length. Allow for either case.
if (is_array($column_name)) {
$real_columns[] = array(
static::_fieldColumnName($field, $column_name[0]),
$column_name[1],
);
}
else {
$real_columns[] = static::_fieldColumnName($field, $column_name);
}
}
$this->database->schema()->addIndex($table, $real_name, $real_columns);
$this->database->schema()->addIndex($revision_table, $real_name, $real_columns);
}
}
}
}
/**
* {@inheritdoc}
*/
public function onFieldDelete(FieldInterface $field) {
// Mark all data associated with the field for deletion.
$field['deleted'] = FALSE;
$table = static::_fieldTableName($field);
$revision_table = static::_fieldRevisionTableName($field);
$this->database->update($table)
->fields(array('deleted' => 1))
->execute();
// Move the table to a unique name while the table contents are being
// deleted.
$field['deleted'] = TRUE;
$new_table = static::_fieldTableName($field);
$revision_new_table = static::_fieldRevisionTableName($field);
$this->database->schema()->renameTable($table, $new_table);
$this->database->schema()->renameTable($revision_table, $revision_new_table);
}
/**
* {@inheritdoc}
*/
public function onInstanceDelete(FieldInstanceInterface $instance) {
$field = $instance->getField();
$table_name = static::_fieldTableName($field);
$revision_name = static::_fieldRevisionTableName($field);
$this->database->update($table_name)
->fields(array('deleted' => 1))
->condition('bundle', $instance['bundle'])
->execute();
$this->database->update($revision_name)
->fields(array('deleted' => 1))
->condition('bundle', $instance['bundle'])
->execute();
}
/**
* {@inheritdoc}
*/
public function onBundleRename($bundle, $bundle_new) {
// We need to account for deleted or inactive fields and instances.
$instances = field_read_instances(array('entity_type' => $this->entityType, 'bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
foreach ($instances as $instance) {
$field = $instance->getField();
if ($field['storage']['type'] == 'field_sql_storage') {
$table_name = static::_fieldTableName($field);
$revision_name = static::_fieldRevisionTableName($field);
$this->database->update($table_name)
->fields(array('bundle' => $bundle_new))
->condition('bundle', $bundle)
->execute();
$this->database->update($revision_name)
->fields(array('bundle' => $bundle_new))
->condition('bundle', $bundle)
->execute();
}
}
}
/**
* {@inheritdoc}
*/
protected function readFieldItemsToPurge(EntityInterface $entity, FieldInstanceInterface $instance) {
$field = $instance->getField();
$table_name = static::_fieldTableName($field);
$query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC))
->condition('entity_id', $entity->id())
->orderBy('delta');
foreach ($field->getColumns() as $column_name => $data) {
$query->addField('t', static::_fieldColumnName($field, $column_name), $column_name);
}
return $query->execute()->fetchAll();
}
/**
* {@inheritdoc}
*/
public function purgeFieldItems(EntityInterface $entity, FieldInstanceInterface $instance) {
$field = $instance->getField();
$table_name = static::_fieldTableName($field);
$revision_name = static::_fieldRevisionTableName($field);
$this->database->delete($table_name)
->condition('entity_id', $entity->id())
->execute();
$this->database->delete($revision_name)
->condition('entity_id', $entity->id())
->execute();
}
/**
* {@inheritdoc}
*/
public function onFieldPurge(FieldInterface $field) {
$table_name = static::_fieldTableName($field);
$revision_name = static::_fieldRevisionTableName($field);
$this->database->schema()->dropTable($table_name);
$this->database->schema()->dropTable($revision_name);
}
/**
* Gets the SQL table schema.
*
* @private Calling this function circumvents the entity system and is
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases.
*
* @param \Drupal\field\FieldInterface $field
* The field object
* @param array $schema
* The field schema array. Mandatory for upgrades, omit otherwise.
*
* @return array
* The same as a hook_schema() implementation for the data and the
* revision tables.
*
* @see hook_schema()
*/
public static function _fieldSqlSchema(FieldInterface $field, array $schema = NULL) {
if ($field['deleted']) {
$description_current = "Data storage for deleted field {$field['id']} ({$field['entity_type']}, {$field['field_name']}).";
$description_revision = "Revision archive storage for deleted field {$field['id']} ({$field['entity_type']}, {$field['field_name']}).";
}
else {
$description_current = "Data storage for {$field['entity_type']} field {$field['field_name']}.";
$description_revision = "Revision archive storage for {$field['entity_type']} field {$field['field_name']}.";
}
$current = array(
'description' => $description_current,
'fields' => array(
'bundle' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
),
'deleted' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => 'A boolean indicating whether this data item has been deleted'
),
'entity_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The entity id this data is attached to',
),
'revision_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
),
'langcode' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => 'The language code for this data item.',
),
'delta' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The sequence number for this data item, used for multi-value fields',
),
),
'primary key' => array('entity_id', 'deleted', 'delta', 'langcode'),
'indexes' => array(
'bundle' => array('bundle'),
'deleted' => array('deleted'),
'entity_id' => array('entity_id'),
'revision_id' => array('revision_id'),
'langcode' => array('langcode'),
),
);
if (!$schema) {
$schema = $field->getSchema();
}
// Add field columns.
foreach ($schema['columns'] as $column_name => $attributes) {
$real_name = static::_fieldColumnName($field, $column_name);
$current['fields'][$real_name] = $attributes;
}
// Add indexes.
foreach ($schema['indexes'] as $index_name => $columns) {
$real_name = static::_fieldIndexName($field, $index_name);
foreach ($columns as $column_name) {
// Indexes can be specified as either a column name or an array with
// column name and length. Allow for either case.
if (is_array($column_name)) {
$current['indexes'][$real_name][] = array(
static::_fieldColumnName($field, $column_name[0]),
$column_name[1],
);
}
else {
$current['indexes'][$real_name][] = static::_fieldColumnName($field, $column_name);
}
}
}
// Add foreign keys.
foreach ($schema['foreign keys'] as $specifier => $specification) {
$real_name = static::_fieldIndexName($field, $specifier);
$current['foreign keys'][$real_name]['table'] = $specification['table'];
foreach ($specification['columns'] as $column_name => $referenced) {
$sql_storage_column = static::_fieldColumnName($field, $column_name);
$current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
}
}
// Construct the revision table.
$revision = $current;
$revision['description'] = $description_revision;
$revision['primary key'] = array('entity_id', 'revision_id', 'deleted', 'delta', 'langcode');
$revision['fields']['revision_id']['not null'] = TRUE;
$revision['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
return array(
static::_fieldTableName($field) => $current,
static::_fieldRevisionTableName($field) => $revision,
);
}
/**
* Generates a table name for a field data table.
*
* @private Calling this function circumvents the entity system and is
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases. Only
* call this function to write a query that \Drupal::entityQuery() does not
* support. Always call entity_load() before using the data found in the
* table.
*
* @param \Drupal\field\FieldInterface $field
* The field object.
*
* @return string
* A string containing the generated name for the database table.
*
*/
static public function _fieldTableName(FieldInterface $field) {
if ($field['deleted']) {
// When a field is a deleted, the table is renamed to
// {field_deleted_data_FIELD_UUID}. To make sure we don't end up with
// table names longer than 64 characters, we hash the uuid and return the
// first 10 characters so we end up with a short unique ID.
return "field_deleted_data_" . substr(hash('sha256', $field['uuid']), 0, 10);
}
else {
return static::_generateFieldTableName($field, FALSE);
}
}
/**
* Generates a table name for a field revision archive table.
*
* @private Calling this function circumvents the entity system and is
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases. Only
* call this function to write a query that Drupal::entityQuery() does not
* support. Always call entity_load() before using the data found in the
* table.
*
* @param \Drupal\field\FieldInterface $field
* The field object.
*
* @return string
* A string containing the generated name for the database table.
*/
static public function _fieldRevisionTableName(FieldInterface $field) {
if ($field['deleted']) {
// When a field is a deleted, the table is renamed to
// {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with
// table names longer than 64 characters, we hash the uuid and return the
// first 10 characters so we end up with a short unique ID.
return "field_deleted_revision_" . substr(hash('sha256', $field['uuid']), 0, 10);
}
else {
return static::_generateFieldTableName($field, TRUE);
}
}
/**
* Generates a safe and unanbiguous field table name.
*
* The method accounts for a maximum table name length of 64 characters, and
* takes care of disambiguation.
*
* @param \Drupal\field\FieldInterface $field
* The field object.
* @param bool $revision
* TRUE for revision table, FALSE otherwise.
*
* @return string
* The final table name.
*/
static protected function _generateFieldTableName($field, $revision) {
$separator = $revision ? '_revision__' : '__';
$table_name = $field->entity_type . $separator . $field->name;
// Limit the string to 48 characters, keeping a 16 characters margin for db
// prefixes.
if (strlen($table_name) > 48) {
// Use a shorter separator, a truncated entity_type, and a hash of the
// field UUID.
$separator = $revision ? '_r__' : '__';
$entity_type = substr($field->entity_type, 0, 38 - strlen($separator));
$field_hash = substr(hash('sha256', $field->uuid), 0, 10);
$table_name = $entity_type . $separator . $field_hash;
}
return $table_name;
}
/**
* Generates an index name for a field data table.
*
* @private Calling this function circumvents the entity system and is
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases.
*
* @param \Drupal\field\FieldInterface $field
* The field structure
* @param string $index
* The name of the index.
*
* @return string
* A string containing a generated index name for a field data table that is
* unique among all other fields.
*/
static public function _fieldIndexName(FieldInterface $field, $index) {
return $field->getFieldName() . '_' . $index;
}
/**
* Generates a column name for a field data table.
*
* @private Calling this function circumvents the entity system and is
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases. Only
* call this function to write a query that \Drupal::entityQuery() does not
* support. Always call entity_load() before using the data found in the
* table.
*
* @param \Drupal\field\FieldInterface $field
* The field object.
* @param string $column
* The name of the column.
*
* @return string
* A string containing a generated column name for a field data table that is
* unique among all other fields.
*/
static public function _fieldColumnName(FieldInterface $field, $column) {
return in_array($column, Field::getReservedColumns()) ? $column : $field->getFieldName() . '_' . $column;
}
}

View File

@ -8,6 +8,7 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Language\Language;
use Drupal\field\FieldInfo;
use PDO;
use Drupal\Core\Entity\Query\QueryInterface;
@ -53,8 +54,8 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
/**
* Overrides DatabaseStorageController::__construct().
*/
public function __construct($entity_type, array $entity_info, Connection $database) {
parent::__construct($entity_type,$entity_info, $database);
public function __construct($entity_type, array $entity_info, Connection $database, FieldInfo $field_info) {
parent::__construct($entity_type,$entity_info, $database, $field_info);
$this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE;
$this->entityClass = $this->entityInfo['class'];
@ -367,6 +368,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
$this->resetCache(array($entity->id()));
$entity->postSave($this, TRUE);
$this->invokeFieldMethod('update', $entity);
$this->saveFieldItems($entity, TRUE);
$this->invokeHook('update', $entity);
if ($this->dataTable) {
$this->invokeTranslationHooks($entity);
@ -389,6 +391,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
$entity->enforceIsNew(FALSE);
$entity->postSave($this, FALSE);
$this->invokeFieldMethod('insert', $entity);
$this->saveFieldItems($entity, FALSE);
$this->invokeHook('insert', $entity);
}
@ -603,6 +606,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
$entity_class::postDelete($this, $entities);
foreach ($entities as $entity) {
$this->invokeFieldMethod('delete', $entity);
$this->deleteFieldItems($entity);
$this->invokeHook('delete', $entity);
}
// Ignore slave server temporarily.

View File

@ -333,8 +333,7 @@ class Entity implements IteratorAggregate, EntityInterface {
// Go through translatable properties and determine all languages for
// which translated values are available.
foreach (field_info_instances($this->entityType, $this->bundle()) as $field_name => $instance) {
$field = field_info_field($field_name);
if (field_is_translatable($this->entityType, $field) && isset($this->$field_name)) {
if (field_is_translatable($this->entityType, $instance->getField()) && isset($this->$field_name)) {
foreach (array_filter($this->$field_name) as $langcode => $value) {
$languages[$langcode] = TRUE;
}

View File

@ -269,6 +269,7 @@ class EntityFormController extends FormBase implements EntityFormControllerInter
*/
public function validate(array $form, array &$form_state) {
$entity = $this->buildEntity($form, $form_state);
$entity_type = $entity->entityType();
$entity_langcode = $entity->language()->id;
$violations = array();
@ -285,9 +286,9 @@ class EntityFormController extends FormBase implements EntityFormControllerInter
else {
// For BC entities, iterate through each field instance and
// instantiate NG items objects manually.
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $field_name => $instance) {
$langcode = field_is_translatable($entity->entityType(), $instance->getField()) ? $entity_langcode : Language::LANGCODE_NOT_SPECIFIED;
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $entity->bundle());
foreach (field_info_instances($entity_type, $entity->bundle()) as $field_name => $instance) {
$langcode = field_is_translatable($entity_type, $instance->getField()) ? $entity_langcode : Language::LANGCODE_NOT_SPECIFIED;
// Create the field object.
$items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
@ -304,7 +305,7 @@ class EntityFormController extends FormBase implements EntityFormControllerInter
// Map errors back to form elements.
if ($violations) {
foreach ($violations as $field_name => $field_violations) {
$langcode = field_is_translatable($entity->entityType(), field_info_field($field_name)) ? $entity_langcode : Language::LANGCODE_NOT_SPECIFIED;
$langcode = field_is_translatable($entity_type , field_info_field($entity_type, $field_name)) ? $entity_langcode : Language::LANGCODE_NOT_SPECIFIED;
$field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
$field_state['constraint_violations'] = $field_violations;
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
@ -433,9 +434,8 @@ class EntityFormController extends FormBase implements EntityFormControllerInter
$current_langcode = $this->isDefaultFormLangcode($form_state) ? $form_state['values']['langcode'] : $this->getFormLangcode($form_state);
foreach (field_info_instances($entity_type, $entity->bundle()) as $instance) {
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
$field = $instance->getField();
$field_name = $field->name;
if (isset($form[$field_name]['#language'])) {
$previous_langcode = $form[$field_name]['#language'];

View File

@ -88,6 +88,15 @@ class EntityManager extends PluginManagerBase {
*/
protected $fieldDefinitions;
/**
* The root paths.
*
* @see \Drupal\Core\Entity\EntityManager::__construct().
*
* @var \Traversable
*/
protected $namespaces;
/**
* The string translationManager.
*
@ -114,22 +123,46 @@ class EntityManager extends PluginManagerBase {
*/
public function __construct(\Traversable $namespaces, ContainerInterface $container, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager, TranslationInterface $translation_manager) {
// Allow the plugin definition to be altered by hook_entity_info_alter().
$annotation_namespaces = array(
'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib',
);
$this->moduleHandler = $module_handler;
$this->cache = $cache;
$this->languageManager = $language_manager;
$this->namespaces = $namespaces;
$this->translationManager = $translation_manager;
$this->doDiscovery($namespaces);
$this->factory = new DefaultFactory($this->discovery);
$this->container = $container;
}
protected function doDiscovery($namespaces) {
$annotation_namespaces = array(
'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib',
);
$this->discovery = new AnnotatedClassDiscovery('Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
$this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
$this->discovery = new AlterDecorator($this->discovery, 'entity_info');
$this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->id, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
}
$this->factory = new DefaultFactory($this->discovery);
$this->container = $container;
/**
* Add more namespaces to the entity manager.
*
* This is usually only necessary for uninstall purposes.
*
* @todo Remove this method, along with doDiscovery(), when
* https://drupal.org/node/1199946 is fixed.
*
* @param \Traversable $namespaces
*
* @see comment_uninstall()
*/
public function addNamespaces(\Traversable $namespaces) {
reset($this->namespaces);
$iterator = new \AppendIterator;
$iterator->append(new \IteratorIterator($this->namespaces));
$iterator->append($namespaces);
$this->doDiscovery($iterator);
}
/**
@ -164,6 +197,9 @@ class EntityManager extends PluginManagerBase {
*/
public function getControllerClass($entity_type, $controller_type, $nested = NULL) {
$definition = $this->getDefinition($entity_type);
if (!$definition) {
throw new \InvalidArgumentException(sprintf('The %s entity type does not exist.', $entity_type));
}
$definition = $definition['controllers'];
if (!$definition) {
throw new \InvalidArgumentException(sprintf('The entity type (%s) does not exist.', $entity_type));

View File

@ -233,6 +233,22 @@ abstract class EntityStorageControllerBase implements EntityStorageControllerInt
}
}
/**
* Invokes a hook on behalf of the entity.
*
* @param string $hook
* One of 'presave', 'insert', 'update', 'predelete', 'delete', or
* 'revision_delete'.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
protected function invokeHook($hook, EntityInterface $entity) {
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity-level hook.
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
}
/**
* Checks translation statuses and invoke the related hooks if needed.
*

View File

@ -0,0 +1,295 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\FieldableEntityStorageControllerBase.
*/
namespace Drupal\Core\Entity;
use Drupal\field\FieldInterface;
use Drupal\field\FieldInstanceInterface;
use Symfony\Component\DependencyInjection\Container;
abstract class FieldableEntityStorageControllerBase extends EntityStorageControllerBase implements FieldableEntityStorageControllerInterface {
/**
* Loads values of configurable fields for a group of entities.
*
* Loads all fields for each entity object in a group of a single entity type.
* The loaded field values are added directly to the entity objects.
*
* This method is a wrapper that handles the field data cache. Subclasses
* need to implement the doLoadFieldItems() method with the actual storage
* logic.
*
* @param array $entities
* An array of entities keyed by entity ID.
* @param int $age
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
* FIELD_LOAD_REVISION to load the version indicated by each entity.
*/
protected function loadFieldItems(array $entities, $age) {
if (empty($entities)) {
return;
}
// Only the most current revision of non-deleted fields for cacheable entity
// types can be cached.
$load_current = $age == FIELD_LOAD_CURRENT;
$info = entity_get_info($this->entityType);
$use_cache = $load_current && $info['field_cache'];
// Ensure we are working with a BC mode entity.
foreach ($entities as $id => $entity) {
$entities[$id] = $entity->getBCEntity();
}
// Assume all entities will need to be queried. Entities found in the cache
// will be removed from the list.
$queried_entities = $entities;
// Fetch available entities from cache, if applicable.
if ($use_cache) {
// Build the list of cache entries to retrieve.
$cids = array();
foreach ($entities as $id => $entity) {
$cids[] = "field:{$this->entityType}:$id";
}
$cache = cache('field')->getMultiple($cids);
// Put the cached field values back into the entities and remove them from
// the list of entities to query.
foreach ($entities as $id => $entity) {
$cid = "field:{$this->entityType}:$id";
if (isset($cache[$cid])) {
unset($queried_entities[$id]);
foreach ($cache[$cid]->data as $field_name => $values) {
$entity->$field_name = $values;
}
}
}
}
// Fetch other entities from their storage location.
if ($queried_entities) {
// Let the storage controller actually load the values.
$this->doLoadFieldItems($queried_entities, $age);
// Invoke the field type's prepareCache() method.
foreach ($queried_entities as $entity) {
$this->invokeFieldItemPrepareCache($entity);
}
// Build cache data.
if ($use_cache) {
foreach ($queried_entities as $id => $entity) {
$data = array();
$instances = field_info_instances($this->entityType, $entity->bundle());
foreach ($instances as $instance) {
$data[$instance['field_name']] = $queried_entities[$id]->{$instance['field_name']};
}
$cid = "field:{$this->entityType}:$id";
cache('field')->set($cid, $data);
}
}
}
}
/**
* Saves values of configurable fields for an entity.
*
* This method is a wrapper that handles the field data cache. Subclasses
* need to implement the doSaveFieldItems() method with the actual storage
* logic.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param bool $update
* TRUE if the entity is being updated, FALSE if it is being inserted.
*/
protected function saveFieldItems(EntityInterface $entity, $update = TRUE) {
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
$this->doSaveFieldItems($entity, $update);
if ($update) {
$entity_info = $entity->entityInfo();
if ($entity_info['field_cache']) {
cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
}
}
}
/**
* Deletes values of configurable fields for all revisions of an entity.
*
* This method is a wrapper that handles the field data cache. Subclasses
* need to implement the doDeleteFieldItems() method with the actual storage
* logic.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
protected function deleteFieldItems(EntityInterface $entity) {
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
$this->doDeleteFieldItems($entity);
$entity_info = $entity->entityInfo();
if ($entity_info['field_cache']) {
cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
}
}
/**
* Deletes values of configurable fields for a single revision of an entity.
*
* This method is a wrapper that handles the field data cache. Subclasses
* need to implement the doDeleteFieldItemsRevision() method with the actual
* storage logic.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity. It must have a revision ID attribute.
*/
protected function deleteFieldItemsRevision(EntityInterface $entity) {
$this->doDeleteFieldItemsRevision($entity->getBCEntity());
}
/**
* Loads values of configurable fields for a group of entities.
*
* This is the method that holds the actual storage logic.
*
* @param array $entities
* An array of entities keyed by entity ID.
* @param int $age
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
* FIELD_LOAD_REVISION to load the version indicated by each entity.
*/
abstract protected function doLoadFieldItems($entities, $age);
/**
* Saves values of configurable fields for an entity.
*
* This is the method that holds the actual storage logic.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param bool $update
* TRUE if the entity is being updated, FALSE if it is being inserted.
*/
abstract protected function doSaveFieldItems(EntityInterface $entity, $update);
/**
* Deletes values of configurable fields for all revisions of an entity.
*
* This is the method that holds the actual storage logic.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
abstract protected function doDeleteFieldItems(EntityInterface $entity);
/**
* Deletes values of configurable fields for a single revision of an entity.
*
* This is the method that holds the actual storage logic.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
abstract protected function doDeleteFieldItemsRevision(EntityInterface $entity);
/**
* {@inheritdoc}
*/
public function onFieldCreate(FieldInterface $field) { }
/**
* {@inheritdoc}
*/
public function onFieldUpdate(FieldInterface $field) { }
/**
* {@inheritdoc}
*/
public function onFieldDelete(FieldInterface $field) { }
/**
* {@inheritdoc}
*/
public function onInstanceCreate(FieldInstanceInterface $instance) { }
/**
* {@inheritdoc}
*/
public function onInstanceUpdate(FieldInstanceInterface $instance) { }
/**
* {@inheritdoc}
*/
public function onInstanceDelete(FieldInstanceInterface $instance) { }
/**
* {@inheritdoc}
*/
public function onBundleCreate($bundle) { }
/**
* {@inheritdoc}
*/
public function onBundleRename($bundle, $bundle_new) { }
/**
* {@inheritdoc}
*/
public function onBundleDelete($bundle) { }
/**
* {@inheritdoc}
*/
public function onFieldItemsPurge(EntityInterface $entity, FieldInstanceInterface $instance) {
if ($values = $this->readFieldItemsToPurge($entity, $instance)) {
$field = $instance->getField();
$definition = _field_generate_entity_field_definition($field, $instance);
$items = \Drupal::typedData()->create($definition, $values, $field->getFieldName(), $entity);
$items->delete();
}
$this->purgeFieldItems($entity, $instance);
}
/**
* Reads values to be purged for a single field of a single entity.
*
* This method is called during field data purge, on fields for which
* onFieldDelete() or onFieldInstanceDelete() has previously run.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param \Drupal\field\FieldInstanceInterface $instance
* The field instance.
*
* @return array
* The field values, in their canonical array format (numerically indexed
* array of items, each item being a property/value array).
*/
abstract protected function readFieldItemsToPurge(EntityInterface $entity, FieldInstanceInterface $instance);
/**
* Removes field data from storage during purge.
*
* @param EntityInterface $entity
* The entity whose values are being purged.
* @param FieldInstanceInterface $instance
* The field whose values are bing purged.
*/
abstract protected function purgeFieldItems(EntityInterface $entity, FieldInstanceInterface $instance);
/**
* {@inheritdoc}
*/
public function onFieldPurge(FieldInterface $field) { }
}

View File

@ -0,0 +1,120 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ExtensibleEntityStorageControllerInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\field\FieldInterface;
use Drupal\field\FieldInstanceInterface;
interface FieldableEntityStorageControllerInterface extends EntityStorageControllerInterface {
/**
* Allows reaction to the creation of a configurable field.
*
* @param \Drupal\field\FieldInterface $field
* The field being created.
*/
public function onFieldCreate(FieldInterface $field);
/**
* Allows reaction to the update of a configurable field.
*
* @param \Drupal\field\FieldInterface $field
* The field being updated.
*/
public function onFieldUpdate(FieldInterface $field);
/**
* Allows reaction to the deletion of a configurable field.
*
* Stored values should not be wiped at once, but marked as 'deleted' so that
* they can go through a proper purge process later on.
*
* @param \Drupal\field\FieldInterface $field
* The field being deleted.
*
* @see fieldPurgeData()
*/
public function onFieldDelete(FieldInterface $field);
/**
* Allows reaction to the creation of a configurable field instance.
*
* @param \Drupal\field\FieldInstanceInterface $instance
* The instance being created.
*/
public function onInstanceCreate(FieldInstanceInterface $instance);
/**
* Allows reaction to the update of a configurable field instance.
*
* @param \Drupal\field\FieldInstanceInterface $instance
* The instance being updated.
*/
public function onInstanceUpdate(FieldInstanceInterface $instance);
/**
* Allows reaction to the deletion of a configurable field instance.
*
* Stored values should not be wiped at once, but marked as 'deleted' so that
* they can go through a proper purge process later on.
*
* @param \Drupal\field\FieldInstanceInterface $instance
* The instance being deleted.
*
* @see fieldPurgeData()
*/
public function onInstanceDelete(FieldInstanceInterface $instance);
/**
* Allows reaction to a bundle being created.
*
* @param string $bundle
* The name of the bundle created.
*/
public function onBundleCreate($bundle);
/**
* Allows reaction to a bundle being renamed.
*
* @param string $bundle
* The name of the bundle being renamed.
* @param string $bundle_new
* The new name of the bundle.
*/
public function onBundleRename($bundle, $bundle_new);
/**
* Allows reaction to a bundle being deleted.
*
* @param string $bundle
* The name of the bundle being deleted.
*/
public function onBundleDelete($bundle);
/**
* Purges the field data for a single field on a single entity.
*
* The entity itself is not being deleted, and it is quite possible that
* other field data will remain attached to it.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose field data is being purged.
* @param \Drupal\field\FieldInstanceInterface $instance
* The deleted field instance whose data is being purged.
*/
public function onFieldItemsPurge(EntityInterface $entity, FieldInstanceInterface $instance);
/**
* Performs final cleanup after all data on all instances has been purged.
*
* @param \Drupal\field\FieldInterface $instance
* The field being purged.
*/
public function onFieldPurge(FieldInterface $field);
}

View File

@ -8,6 +8,7 @@
namespace Drupal\Core\Entity\Query\Sql;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
use Drupal\Core\Entity\Query\QueryException;
use Drupal\field\Entity\Field;
@ -102,8 +103,8 @@ class Tables implements TablesInterface {
if (substr($specifier, 0, 3) == 'id:') {
$field = $field_info->getFieldById((substr($specifier, 3)));
}
elseif (isset($field_map[$specifier])) {
$field = $field_info->getField($specifier);
elseif (isset($field_map[$entity_type][$specifier])) {
$field = $field_info->getField($entity_type, $specifier);
}
else {
$field = FALSE;
@ -136,7 +137,7 @@ class Tables implements TablesInterface {
$field_name = $field->getFieldName();
// If there are bundles, pick one.
if (!empty($entity_info['entity_keys']['bundle'])) {
$values[$entity_info['entity_keys']['bundle']] = reset($field_map[$field_name]['bundles'][$entity_type]);
$values[$entity_info['entity_keys']['bundle']] = reset($field_map[$entity_type][$field_name]['bundles']);
}
$entity = $entity_manager
->getStorageController($entity_type)
@ -161,7 +162,7 @@ class Tables implements TablesInterface {
$column = 'value';
}
$table = $this->ensureFieldTable($index_prefix, $field, $type, $langcode, $base_table, $entity_id_field, $field_id_field);
$sql_column = _field_sql_storage_columnname($field['field_name'], $column);
$sql_column = DatabaseStorageController::_fieldColumnName($field, $column);
}
// This is an entity property (non-configurable field).
else {
@ -250,12 +251,12 @@ class Tables implements TablesInterface {
protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $base_table, $entity_id_field, $field_id_field) {
$field_name = $field['field_name'];
if (!isset($this->fieldTables[$index_prefix . $field_name])) {
$table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
$table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? DatabaseStorageController::_fieldTableName($field) : DatabaseStorageController::_fieldRevisionTableName($field);
if ($field['cardinality'] != 1) {
$this->sqlQuery->addMetaData('simple_query', FALSE);
}
$entity_type = $this->sqlQuery->getMetaData('entity_type');
$this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field AND %alias.entity_type = '$entity_type'", $langcode);
$this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field", $langcode);
}
return $this->fieldTables[$index_prefix . $field_name];
}

View File

@ -35,6 +35,10 @@ function block_update_dependencies() {
$dependencies['block'][8005] = array(
'user' => 8016,
);
// Migrate custom blocks after field storage has been reorganized.
$dependencies['block'][8008] = array(
'field' => 8006,
);
return $dependencies;
}
@ -247,9 +251,9 @@ function block_update_8008() {
// First, create the body field.
$body_field = array(
'id' => 'block_body',
'name' => 'block_body',
'entity_type' => 'custom_block',
'type' => 'text_with_summary',
'entity_types' => array('custom_block'),
'module' => 'text',
'cardinality' => 1,
'schema' => array(
@ -283,8 +287,8 @@ function block_update_8008() {
);
_update_8003_field_create_field($body_field);
$instance = array(
'id' => 'custom_block.basic.block_body',
'entity_type' => 'custom_block',
'bundle' => 'basic',
'label' => 'Block body',
@ -338,7 +342,7 @@ function block_update_8008() {
);
// This is a core update and no contrib modules are enabled yet, so
// we can assume default field storage for a faster update.
_update_8000_field_sql_storage_write('custom_block', 'basic', $block->bid, $block->bid, 'block_body', $data);
_update_8006_field_write_data_sql('custom_block', 'basic', $block->bid, $block->bid, 'block_body', $data);
$sandbox['last'] = $block->bid;
$sandbox['count'] += 1;

View File

@ -203,13 +203,13 @@ function custom_block_entity_bundle_info() {
*/
function custom_block_add_body_field($block_type_id, $label = 'Block body') {
// Add or remove the body field, as needed.
$field = field_info_field('block_body');
$field = field_info_field('custom_block', 'block_body');
$instance = field_info_instance('custom_block', 'block_body', $block_type_id);
if (empty($field)) {
$field = entity_create('field_entity', array(
'field_name' => 'block_body',
'name' => 'block_body',
'entity_type' => 'custom_block',
'type' => 'text_with_summary',
'entity_types' => array('custom_block'),
));
$field->save();
}

View File

@ -16,7 +16,7 @@ use Drupal\Core\Entity\EntityStorageControllerInterface;
* This extends the Drupal\Core\Entity\DatabaseStorageControllerNG class,
* adding required special handling for custom block entities.
*/
class CustomBlockStorageController extends DatabaseStorageControllerNG implements EntityStorageControllerInterface {
class CustomBlockStorageController extends DatabaseStorageControllerNG {
/**
* Overrides \Drupal\Core\Entity\DatabaseStorageController::attachLoad().

View File

@ -65,13 +65,14 @@ class CustomBlockFieldTest extends CustomBlockTestBase {
// Create a field with settings to validate.
$this->field = entity_create('field_entity', array(
'field_name' => drupal_strtolower($this->randomName()),
'name' => drupal_strtolower($this->randomName()),
'entity_type' => 'custom_block',
'type' => 'link',
'cardinality' => 2,
));
$this->field->save();
$this->instance = entity_create('field_instance', array(
'field_name' => $this->field->id(),
'field_name' => $this->field->name,
'entity_type' => 'custom_block',
'bundle' => 'link',
'settings' => array(

View File

@ -160,9 +160,8 @@ function hook_comment_unpublish(Drupal\comment\Comment $comment) {
/**
* Act before comment deletion.
*
* This hook is invoked from entity_delete_multiple() before
* field_attach_delete() is called and before the comment is actually removed
* from the database.
* This hook is invoked from entity_delete_multiple() before field values are
* deleted and before the comment is actually removed from the database.
*
* @param Drupal\comment\Comment $comment
* The comment object for the comment that is about to be deleted.
@ -180,9 +179,8 @@ function hook_comment_predelete(Drupal\comment\Comment $comment) {
/**
* Respond to comment deletion.
*
* This hook is invoked from entity_delete_multiple() after
* field_attach_delete() has called and after the comment has been removed from
* the database.
* This hook is invoked from entity_delete_multiple() after field values are
* deleted and after the comment has been removed from the database.
*
* @param Drupal\comment\Comment $comment
* The comment object for the comment that has been deleted.

View File

@ -12,6 +12,10 @@ function comment_uninstall() {
// Remove variables.
variable_del('comment_block_count');
$node_types = array_keys(node_type_get_types());
Drupal::entityManager()->addNamespaces(new ArrayIterator(array(
'Drupal\comment' => DRUPAL_ROOT . '/core/modules/comment/lib',
)));
drupal_classloader_register('comment', 'core/modules/comment');
foreach ($node_types as $node_type) {
entity_invoke_bundle_hook('delete', 'comment', 'comment_node_' . $node_type);
variable_del('comment_' . $node_type);

View File

@ -318,11 +318,11 @@ function comment_node_type_delete($info) {
*/
function _comment_body_field_create($info) {
// Create the field if needed.
if (!field_read_field('comment_body', array('include_inactive' => TRUE))) {
if (!field_read_field('comment', 'comment_body', array('include_inactive' => TRUE))) {
$field = entity_create('field_entity', array(
'field_name' => 'comment_body',
'name' => 'comment_body',
'type' => 'text_long',
'entity_types' => array('comment'),
'entity_type' => 'comment',
));
$field->save();
}

View File

@ -45,7 +45,7 @@ class CommentFieldsTest extends CommentTestBase {
}
// Check that the 'comment_body' field is deleted.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertTrue(empty($field), 'The comment_body field was deleted');
// Create a new content type.
@ -54,7 +54,7 @@ class CommentFieldsTest extends CommentTestBase {
// Check that the 'comment_body' field exists and has an instance on the
// new comment bundle.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertTrue($field, 'The comment_body field exists');
$instances = field_info_instances('comment');
$this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name)));

View File

@ -71,7 +71,7 @@ class CommentLanguageTest extends WebTestBase {
$this->drupalPost("user/" . $admin_user->id() . "/edit", $edit, t('Save'));
// Make comment body translatable.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$field['translatable'] = TRUE;
$field->save();
$this->assertTrue(field_is_translatable('comment', $field), 'Comment body is translatable.');

View File

@ -63,7 +63,7 @@ class CommentTranslationUITest extends ContentTranslationUITest {
*/
function setupTestFields() {
parent::setupTestFields();
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$field['translatable'] = TRUE;
$field->save();
}

View File

@ -42,7 +42,7 @@ class CommentUninstallTest extends WebTestBase {
*/
function testCommentUninstallWithField() {
// Ensure that the field exists before uninstallation.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertNotNull($field, 'The comment_body field exists.');
// Uninstall the comment module which should trigger field deletion.
@ -50,7 +50,7 @@ class CommentUninstallTest extends WebTestBase {
$this->container->get('module_handler')->uninstall(array('comment'));
// Check that the field is now deleted.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertNull($field, 'The comment_body field has been deleted.');
}
@ -60,12 +60,12 @@ class CommentUninstallTest extends WebTestBase {
*/
function testCommentUninstallWithoutField() {
// Manually delete the comment_body field before module uninstallation.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertNotNull($field, 'The comment_body field exists.');
$field->delete();
// Check that the field is now deleted.
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertNull($field, 'The comment_body field has been deleted.');
// Ensure that uninstallation succeeds even if the field has already been

View File

@ -21,7 +21,7 @@ class ConfigLocaleOverride extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('locale', 'config_test', 'user', 'language', 'system');
public static $modules = array('locale', 'config_test', 'user', 'language', 'system', 'field');
public static function getInfo() {
return array(

View File

@ -21,7 +21,7 @@ class MessageEntityTest extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('system', 'contact');
public static $modules = array('system', 'contact', 'field');
public static function getInfo() {
return array(

View File

@ -7,6 +7,7 @@
namespace Drupal\contact\Tests\Views;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\views\Tests\ViewTestBase;
/**
@ -40,13 +41,14 @@ class ContactFieldsTest extends ViewTestBase {
parent::setUp();
$this->field = entity_create('field_entity', array(
'field_name' => strtolower($this->randomName()),
'name' => strtolower($this->randomName()),
'entity_type' => 'contact_message',
'type' => 'text'
));
$this->field->save();
entity_create('field_instance', array(
'field_name' => $this->field->id(),
'field_name' => $this->field->name,
'entity_type' => 'contact_message',
'bundle' => 'contact_message',
))->save();
@ -58,16 +60,11 @@ class ContactFieldsTest extends ViewTestBase {
* Tests the views data generation.
*/
public function testViewsData() {
$table_name = _field_sql_storage_tablename($this->field);
// Test that the field is not exposed to views, since contact_message
// entities have no storage.
$table_name = DatabaseStorageController::_fieldTableName($this->field);
$data = $this->container->get('views.views_data')->get($table_name);
// Test that the expected data array is returned.
$expected = array('', '_value', '_format');
$this->assertEqual(count($data), count($expected), 'The expected amount of array keys were found.');
foreach ($expected as $suffix) {
$this->assertTrue(isset($data[$this->field->id() . $suffix]));
}
$this->assertTrue(empty($data['table']['join']), 'The field is not joined to the non existent contact message base table.');
$this->assertFalse($data, 'The field is not exposed to Views.');
}
}

View File

@ -95,7 +95,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
// @todo Exploit field definitions once all core entities and field
// types are migrated to the Entity Field API.
foreach ($fields as $field_name => $instance) {
$field = field_info_field($field_name);
$field = $instance->getField();
$form['settings'][$entity_type][$bundle]['fields'][$field_name] = array(
'#label' => $instance['label'],
'#type' => 'checkbox',
@ -326,7 +326,7 @@ function _content_translation_update_field_translatability($settings) {
foreach ($bundle_settings['fields'] as $field_name => $translatable) {
// If a field is enabled for translation for at least one instance we
// need to mark it as translatable.
$fields[$field_name] = $translatable || !empty($fields[$field_name]);
$fields[$entity_type][$field_name] = $translatable || !empty($fields[$entity_type][$field_name]);
}
}
// @todo Store non-configurable field settings to be able to alter their
@ -335,22 +335,24 @@ function _content_translation_update_field_translatability($settings) {
}
$operations = array();
foreach ($fields as $field_name => $translatable) {
$field = field_info_field($field_name);
if ($field['translatable'] != $translatable) {
// If a field is untranslatable, it can have no data except under
// Language::LANGCODE_NOT_SPECIFIED. Thus we need a field to be translatable before
// we convert data to the entity language. Conversely we need to switch
// data back to Language::LANGCODE_NOT_SPECIFIED before making a field
// untranslatable lest we lose information.
$field_operations = array(
array('content_translation_translatable_switch', array($translatable, $field_name)),
);
if ($field->hasData()) {
$field_operations[] = array('content_translation_translatable_batch', array($translatable, $field_name));
$field_operations = $translatable ? $field_operations : array_reverse($field_operations);
foreach ($fields as $entity_type => $entity_type_fields) {
foreach ($entity_type_fields as $field_name => $translatable) {
$field = field_info_field($entity_type, $field_name);
if ($field['translatable'] != $translatable) {
// If a field is untranslatable, it can have no data except under
// Language::LANGCODE_NOT_SPECIFIED. Thus we need a field to be translatable before
// we convert data to the entity language. Conversely we need to switch
// data back to Language::LANGCODE_NOT_SPECIFIED before making a field
// untranslatable lest we lose information.
$field_operations = array(
array('content_translation_translatable_switch', array($translatable, $entity_type, $field_name)),
);
if ($field->hasData()) {
$field_operations[] = array('content_translation_translatable_batch', array($translatable, $field_name));
$field_operations = $translatable ? $field_operations : array_reverse($field_operations);
}
$operations = array_merge($operations, $field_operations);
}
$operations = array_merge($operations, $field_operations);
}
}
@ -374,11 +376,13 @@ function _content_translation_update_field_translatability($settings) {
* @param bool $translatable
* Indicator of whether the field should be made translatable (TRUE) or
* untranslatble (FALSE).
* @param string $entity_type
* Field entity type.
* @param string $field_name
* Field machine name.
*/
function content_translation_translatable_switch($translatable, $field_name) {
$field = field_info_field($field_name);
function content_translation_translatable_switch($translatable, $entity_type, $field_name) {
$field = field_info_field($entity_type, $field_name);
if ($field['translatable'] !== $translatable) {
$field['translatable'] = $translatable;
$field->save();
@ -395,10 +399,6 @@ function content_translation_translatable_switch($translatable, $field_name) {
* Field machine name.
*/
function content_translation_translatable_batch($translatable, $field_name, &$context) {
$field = field_info_field($field_name);
$column = isset($field['columns']['value']) ? 'value' : key($field['columns']);
$query_field = "$field_name.$column";
// Determine the entity types to act on.
$entity_types = array();
foreach (field_info_instances() as $entity_type => $info) {
@ -417,6 +417,10 @@ function content_translation_translatable_batch($translatable, $field_name, &$co
$context['sandbox']['max'] = 0;
foreach ($entity_types as $entity_type) {
$field = field_info_field($entity_type, $field_name);
$column = isset($field['columns']['value']) ? 'value' : key($field['columns']);
$query_field = "$field_name.$column";
// How many entities will need processing?
$query = Drupal::entityQuery($entity_type);
$count = $query
@ -444,6 +448,9 @@ function content_translation_translatable_batch($translatable, $field_name, &$co
$info = entity_get_info($entity_type);
$offset = $context['sandbox']['progress_entity_type'][$entity_type];
$query = Drupal::entityQuery($entity_type);
$field = field_info_field($entity_type, $field_name);
$column = isset($field['columns']['value']) ? 'value' : key($field['columns']);
$query_field = "$field_name.$column";
$result = $query
->exists($query_field)
->sort($info['entity_keys']['id'])

View File

@ -200,7 +200,7 @@ function content_translation_menu() {
}
}
$items['admin/config/regional/content_translation/translatable/%'] = array(
$items['admin/config/regional/content_translation/translatable/%/%'] = array(
'title' => 'Confirm change in translatability.',
'description' => 'Confirm page for changing field translatability.',
'route_name' => 'content_translation_translatable',
@ -621,7 +621,7 @@ function content_translation_form_alter(array &$form, array &$form_state) {
else {
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
$field = $instance->getField();
$form[$field_name]['#multilingual'] = !empty($field['translatable']);
}
}
@ -808,6 +808,7 @@ function content_translation_field_extra_fields() {
*/
function content_translation_form_field_ui_field_edit_form_alter(array &$form, array &$form_state, $form_id) {
$field = $form['#field'];
$entity_type = $field['entity_type'];
$field_name = $field['field_name'];
$translatable = $field['translatable'];
$label = t('Field translation');
@ -821,7 +822,7 @@ function content_translation_form_field_ui_field_edit_form_alter(array &$form, a
'#type' => 'link',
'#prefix' => t('This field has data in existing content.') . ' ',
'#title' => !$translatable ? t('Enable translation') : t('Disable translation'),
'#href' => 'admin/config/regional/content_translation/translatable/' . $field_name,
'#href' => "admin/config/regional/content_translation/translatable/$entity_type/$field_name",
'#options' => array('query' => drupal_get_destination()),
'#access' => user_access('administer content translation'),
),
@ -1022,7 +1023,7 @@ function content_translation_save_settings($settings) {
// Store whether fields have translation enabled or not.
if (!empty($bundle_settings['columns'])) {
foreach ($bundle_settings['columns'] as $field_name => $column_settings) {
$field = field_info_field($field_name);
$field = field_info_field($entity_type, $field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
if ($field['translatable']) {
$instance['settings']['translation_sync'] = $column_settings;

View File

@ -40,8 +40,7 @@ function content_translation_overview(EntityInterface $entity) {
// Determine whether the current entity is translatable.
$translatable = FALSE;
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
$field = $instance->getField();
if ($field['translatable']) {
$translatable = TRUE;
break;
@ -246,7 +245,7 @@ function content_translation_prepare_translation(EntityInterface $entity, Langua
else {
$instances = field_info_instances($entity->entityType(), $entity->bundle());
foreach ($instances as $field_name => $instance) {
$field = field_info_field($field_name);
$field = $instance->getField();
if (!empty($field['translatable'])) {
$value = $entity->get($field_name);
$value[$target->id] = isset($value[$source->id]) ? $value[$source->id] : array();

View File

@ -1,5 +1,5 @@
content_translation_translatable:
pattern: 'admin/config/regional/content_translation/translatable/{field_name}'
pattern: 'admin/config/regional/content_translation/translatable/{entity_type}/{field_name}'
defaults:
_form: 'Drupal\content_translation\Form\TranslatableForm'
requirements:

View File

@ -51,7 +51,7 @@ class ContentTranslationController implements ContentTranslationControllerInterf
// Remove field translations.
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
$field = $instance->getField();
if ($field['translatable']) {
$entity->{$field_name}[$langcode] = array();
}

View File

@ -8,7 +8,8 @@
namespace Drupal\content_translation\Form;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\field\Field;
use Drupal\field\Entity\Field;
use Drupal\field\Field as FieldInfo;
/**
* Provides a confirm form for changing translatable status on translation
@ -19,9 +20,9 @@ class TranslatableForm extends ConfirmFormBase {
/**
* The field info we are changing translatable status on.
*
* @var array.
* @var \Drupal\field\Entity\Field
*/
protected $fieldInfo;
protected $field;
/**
* The field name we are changing translatable
@ -42,7 +43,7 @@ class TranslatableForm extends ConfirmFormBase {
* {@inheritdoc}
*/
public function getQuestion() {
if ($field['translatable']) {
if ($this->field['translatable']) {
$question = t('Are you sure you want to disable translation for the %name field?', array('%name' => $this->fieldName));
}
else {
@ -58,7 +59,7 @@ class TranslatableForm extends ConfirmFormBase {
$description = t('By submitting this form these changes will apply to the %name field everywhere it is used.',
array('%name' => $this->fieldName)
);
$description .= $this->fieldInfo['translatable'] ? "<br>" . t("<strong>All the existing translations of this field will be deleted.</strong><br>This action cannot be undone.") : '';
$description .= $this->field['translatable'] ? "<br>" . t("<strong>All the existing translations of this field will be deleted.</strong><br>This action cannot be undone.") : '';
return $description;
}
@ -71,12 +72,14 @@ class TranslatableForm extends ConfirmFormBase {
/**
* {@inheritdoc}
* @param string $entity_type
* The entity type.
* @param string $field_name
* The field name.
*/
public function buildForm(array $form, array &$form_state, $field_name = NULL) {
public function buildForm(array $form, array &$form_state, $entity_type = NULL, $field_name = NULL) {
$this->fieldName = $field_name;
$this->fieldInfo = Field::fieldInfo($field_name);
$this->fieldInfo = FieldInfo::fieldInfo()->getField($entity_type, $field_name);
return parent::buildForm($form, $form_state);
}
@ -127,6 +130,7 @@ class TranslatableForm extends ConfirmFormBase {
array(
'content_translation_translatable_switch', array(
!$translatable,
$this->field['entity_type'],
$this->fieldName,
),
),

View File

@ -89,7 +89,7 @@ class ContentTranslationSettingsTest extends WebTestBase {
);
$this->assertSettings('comment', 'comment_node_article', TRUE, $edit);
field_info_cache_clear();
$field = field_info_field('comment_body');
$field = field_info_field('comment', 'comment_body');
$this->assertTrue($field['translatable'], 'Comment body is translatable.');
// Test that language settings are correctly stored.

View File

@ -57,7 +57,8 @@ class ContentTranslationSyncImageTest extends ContentTranslationTestBase {
$this->cardinality = 3;
entity_create('field_entity', array(
'field_name' => $this->fieldName,
'name' => $this->fieldName,
'entity_type' => $this->entityType,
'type' => 'image',
'cardinality' => $this->cardinality,
'translatable' => TRUE,

View File

@ -162,8 +162,9 @@ abstract class ContentTranslationTestBase extends WebTestBase {
$this->fieldName = 'field_test_et_ui_test';
entity_create('field_entity', array(
'field_name' => $this->fieldName,
'name' => $this->fieldName,
'type' => 'text',
'entity_type' => $this->entityType,
'cardinality' => 1,
'translatable' => TRUE,
))->save();

View File

@ -36,8 +36,9 @@ class DateTimeItemTest extends FieldUnitTestBase {
// Create a field with settings to validate.
$this->field = entity_create('field_entity', array(
'field_name' => 'field_datetime',
'name' => 'field_datetime',
'type' => 'datetime',
'entity_type' => 'entity_test',
'settings' => array('datetime_type' => 'date'),
));
$this->field->save();

View File

@ -57,13 +57,14 @@ class DatetimeFieldTest extends WebTestBase {
// Create a field with settings to validate.
$this->field = entity_create('field_entity', array(
'field_name' => drupal_strtolower($this->randomName()),
'name' => drupal_strtolower($this->randomName()),
'entity_type' => 'entity_test',
'type' => 'datetime',
'settings' => array('datetime_type' => 'date'),
));
$this->field->save();
$this->instance = entity_create('field_instance', array(
'field_name' => $this->field->id(),
'field_name' => $this->field->name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'settings' => array(
@ -73,7 +74,7 @@ class DatetimeFieldTest extends WebTestBase {
$this->instance->save();
entity_get_form_display($this->instance->entity_type, $this->instance->bundle, 'default')
->setComponent($this->field->id(), array(
->setComponent($this->field->name, array(
'type' => 'datetime_default',
))
->save();
@ -84,7 +85,7 @@ class DatetimeFieldTest extends WebTestBase {
'settings' => array('format_type' => 'medium'),
);
entity_get_display($this->instance->entity_type, $this->instance->bundle, 'full')
->setComponent($this->field->id(), $this->display_options)
->setComponent($this->field->name, $this->display_options)
->save();
}
@ -92,7 +93,7 @@ class DatetimeFieldTest extends WebTestBase {
* Tests date field functionality.
*/
function testDateField() {
$field_name = $this->field->id();
$field_name = $this->field->name;
// Display creation form.
$this->drupalGet('entity_test/add');
@ -160,7 +161,7 @@ class DatetimeFieldTest extends WebTestBase {
* Tests date and time field.
*/
function testDatetimeField() {
$field_name = $this->field->id();
$field_name = $this->field->name;
// Change the field to a datetime field.
$this->field['settings']['datetime_type'] = 'datetime';
$this->field->save();
@ -229,7 +230,7 @@ class DatetimeFieldTest extends WebTestBase {
* Tests Date List Widget functionality.
*/
function testDatelistWidget() {
$field_name = $this->field->id();
$field_name = $this->field->name;
// Change the field to a datetime field.
$this->field->settings['datetime_type'] = 'datetime';
$this->field->save();
@ -299,7 +300,7 @@ class DatetimeFieldTest extends WebTestBase {
// Change the field to a datetime field.
$this->field->settings['datetime_type'] = 'datetime';
$this->field->save();
$field_name = $this->field->id();
$field_name = $this->field->name;
// Set the default value to 'now'.
$this->instance->settings['default_value'] = 'now';
@ -341,7 +342,7 @@ class DatetimeFieldTest extends WebTestBase {
// Change the field to a datetime field.
$this->field->settings['datetime_type'] = 'datetime';
$this->field->save();
$field_name = $this->field->id();
$field_name = $this->field->name;
// Display creation form.
$this->drupalGet('entity_test/add');

View File

@ -71,7 +71,8 @@ class EditEntityFieldAccessCheck implements StaticAccessCheckInterface, EditEnti
* {@inheritdoc}
*/
public function accessEditEntityField(EntityInterface $entity, $field_name) {
return $entity->access('update') && ($field = $this->fieldInfo->getField($field_name)) && field_access('edit', $field, $entity->entityType(), $entity);
$entity_type = $entity->entityType();
return $entity->access('update') && ($field = $this->fieldInfo->getField($entity_type, $field_name)) && field_access('edit', $field, $entity_type, $entity);
}
/**

View File

@ -19,7 +19,8 @@ class EditTestBase extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('system', 'entity', 'entity_test', 'field', 'field_sql_storage', 'field_test', 'number', 'filter', 'user', 'text', 'edit');
public static $modules = array('system', 'entity', 'entity_test', 'field', 'field_test', 'number', 'filter', 'user', 'text', 'edit');
/**
* Sets the default field storage backend for fields created during tests.
*/
@ -55,7 +56,8 @@ class EditTestBase extends DrupalUnitTestBase {
function createFieldWithInstance($field_name, $type, $cardinality, $label, $instance_settings, $widget_type, $widget_settings, $formatter_type, $formatter_settings) {
$field = $field_name . '_field';
$this->$field = entity_create('field_entity', array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => $type,
'cardinality' => $cardinality,
));

View File

@ -62,7 +62,8 @@ class EmailFieldTest extends WebTestBase {
// Create a field with settings to validate.
$field_name = drupal_strtolower($this->randomName());
$this->field = entity_create('field_entity', array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'email',
));
$this->field->save();

View File

@ -36,7 +36,8 @@ class EmailItemTest extends FieldUnitTestBase {
// Create an email field and instance for validation.
entity_create('field_entity', array(
'field_name' => 'field_email',
'name' => 'field_email',
'entity_type' => 'entity_test',
'type' => 'email',
))->save();
entity_create('field_instance', array(

View File

@ -209,7 +209,7 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
}
if ($instance = field_info_instance($this->targetEntityType, $name, $this->bundle)) {
$field = field_info_field($instance['field_name']);
$field = $instance->getField();
$options = $this->pluginManager->prepareConfiguration($field['type'], $options);
// Clear the persisted plugin, if any.

View File

@ -131,7 +131,7 @@ class EntityDisplayTest extends DrupalUnitTestBase {
* Tests the behavior of a field component within an EntityDisplay object.
*/
public function testFieldComponent() {
$this->enableModules(array('field_sql_storage', 'field_test'));
$this->enableModules(array('field_test'));
$display = entity_create('entity_display', array(
'targetEntityType' => 'entity_test',
@ -142,7 +142,8 @@ class EntityDisplayTest extends DrupalUnitTestBase {
$field_name = 'test_field';
// Create a field and an instance.
$field = entity_create('field_entity', array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'test_field'
));
$field->save();
@ -202,7 +203,7 @@ class EntityDisplayTest extends DrupalUnitTestBase {
* Tests renaming and deleting a bundle.
*/
public function testRenameDeleteBundle() {
$this->enableModules(array('field_sql_storage', 'field_test', 'node', 'system', 'text'));
$this->enableModules(array('field_test', 'node', 'system', 'text'));
$this->installSchema('system', array('variable'));
$this->installSchema('node', array('node'));
@ -239,12 +240,13 @@ class EntityDisplayTest extends DrupalUnitTestBase {
* Tests deleting field instance.
*/
public function testDeleteFieldInstance() {
$this->enableModules(array('field_sql_storage', 'field_test'));
$this->enableModules(array('field_test'));
$field_name = 'test_field';
// Create a field and an instance.
$field = entity_create('field_entity', array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'test_field'
));
$field->save();

View File

@ -53,7 +53,7 @@ class EntityFormDisplayTest extends DrupalUnitTestBase {
* Tests the behavior of a field component within an EntityFormDisplay object.
*/
public function testFieldComponent() {
$this->enableModules(array('field_sql_storage', 'field_test'));
$this->enableModules(array('field_test'));
$form_display = entity_create('entity_form_display', array(
'targetEntityType' => 'entity_test',
@ -64,7 +64,8 @@ class EntityFormDisplayTest extends DrupalUnitTestBase {
// Create a field and an instance.
$field_name = 'test_field';
$field = entity_create('field_entity', array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'test_field'
));
$field->save();

View File

@ -37,7 +37,7 @@ function entity_reference_field_info_alter(&$info) {
function entity_reference_entity_field_info_alter(&$info, $entity_type) {
foreach (field_info_instances($entity_type) as $bundle_name => $instances) {
foreach ($instances as $field_name => $instance) {
$field = field_info_field($field_name);
$field = $instance->getField();
if ($field['type'] != 'entity_reference') {
continue;
}
@ -84,7 +84,7 @@ function entity_reference_field_entity_update(FieldInterface $field) {
return;
}
$field_name = $field->id();
$field_name = $field->getFieldName();
foreach ($field->bundles() as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
@ -225,14 +225,14 @@ function entity_reference_create_instance($entity_type, $bundle, $field_name, $f
}
// Look for or add the specified field to the requested entity bundle.
$field = field_info_field($field_name);
$field = field_info_field($entity_type, $field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
if (empty($field)) {
$field = array(
'field_name' => $field_name,
'name' => $field_name,
'type' => 'entity_reference',
'entity_types' => array($entity_type),
'entity_type' => $entity_type,
'settings' => array(
'target_type' => $target_entity_type,
),

View File

@ -69,7 +69,7 @@ class EntityReferenceController implements ControllerInterface {
* The matched labels as json.
*/
public function handleAutocomplete(Request $request, $type, $field_name, $entity_type, $bundle_name, $entity_id) {
if (!$field = field_info_field($field_name)) {
if (!$field = field_info_field($entity_type, $field_name)) {
throw new AccessDeniedHttpException();
}

View File

@ -107,7 +107,7 @@ class SelectionBase implements SelectionInterface {
$fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info['base_table']));
foreach (field_info_instances($target_type) as $bundle_instances) {
foreach ($bundle_instances as $instance_name => $instance_info) {
$field_info = field_info_field($instance_name);
$field_info = $instance_info->getField();
foreach ($field_info['columns'] as $column_name => $column_info) {
$fields[$instance_name . '.' . $column_name] = t('@label (@column)', array('@label' => $instance_info['label'], '@column' => $column_name));
}

View File

@ -36,12 +36,13 @@ class EntityReferenceAutoCreateTest extends WebTestBase {
$this->referenced_type = $referenced->type;
entity_create('field_entity', array(
'name' => 'test_field',
'entity_type' => 'node',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'node',
),
'field_name' => 'test_field',
'type' => 'entity_reference',
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
))->save();

View File

@ -89,7 +89,7 @@ class EntityReferenceFieldTest extends EntityUnitTestBase {
array('target_bundles' => array($this->bundle))
);
$this->field = Field::fieldInfo()->getField($this->fieldName);
$this->field = Field::fieldInfo()->getField($this->entityType, $this->fieldName);
$instances = Field::fieldInfo()->getBundleInstances($this->entityType, $this->bundle);
$this->instance = $instances[$this->fieldName];
}

View File

@ -7,6 +7,7 @@
namespace Drupal\entity_reference\Tests;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\Field\FieldInterface;
use Drupal\Core\Entity\Field\FieldItemInterface;
use Drupal\Core\Language\Language;
@ -110,16 +111,16 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
* Tests foreign key support.
*/
public function testEntityReferenceFieldSchema() {
$field = field_info_field('field_test_taxonomy');
$field = field_info_field('entity_test', 'field_test_taxonomy');
$foreign_key_column_name = 'target_id';
// Grab the SQL schema and verify that the 'foreign keys' are present.
$schemas = _field_sql_storage_schema($field);
$schema = $schemas[_field_sql_storage_tablename($field)];
$schemas = DatabaseStorageController::_fieldSqlSchema($field);
$schema = $schemas[DatabaseStorageController::_fieldTableName($field)];
$this->assertEqual(count($schema['foreign keys']), 1, 'There is 1 foreign key in the schema.');
$foreign_key = reset($schema['foreign keys']);
$foreign_key_column = _field_sql_storage_columnname($field['field_name'], $foreign_key_column_name);
$foreign_key_column = DatabaseStorageController::_fieldColumnName($field, $foreign_key_column_name);
$this->assertEqual($foreign_key['table'], 'taxonomy_term_data', 'Foreign key table name preserved in the schema.');
$this->assertEqual($foreign_key['columns'][$foreign_key_column], 'tid', 'Foreign key column name preserved in the schema.');
@ -127,10 +128,10 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
// foreign key is present.
$field_name = 'field_test_vocabulary';
entity_reference_create_instance('entity_test', 'entity_test', $field_name, 'Test vocabulary reference', 'taxonomy_vocabulary');
$field = field_info_field($field_name);
$field = field_info_field('entity_test', $field_name);
$schemas = _field_sql_storage_schema($field);
$schema = $schemas[_field_sql_storage_tablename($field)];
$schemas = DatabaseStorageController::_fieldSqlSchema($field);
$schema = $schemas[DatabaseStorageController::_fieldTableName($field)];
$this->assertFalse(isset($schema['foreign keys']), 'There is no foreign key in the schema.');
}
}

View File

@ -24,7 +24,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
);
}
public static $modules = array('node', 'comment', 'entity_reference');
public static $modules = array('node', 'comment', 'entity_reference', 'entity_test');
function setUp() {
parent::setUp();
@ -61,19 +61,20 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
public function testNodeHandler() {
// Create a field and instance.
$field = entity_create('field_entity', array(
'name' => 'test_field',
'entity_type' => 'entity_test',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'node',
),
'field_name' => 'test_field',
'type' => 'entity_reference',
'cardinality' => '1',
));
$field->save();
$instance = entity_create('field_instance', array(
'field_name' => 'test_field',
'entity_type' => 'test_entity',
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'default',
@ -205,19 +206,19 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
public function testUserHandler() {
// Create a field and instance.
$field = entity_create('field_entity', array(
'name' => 'test_field',
'entity_type' => 'entity_test',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'user',
),
'field_name' => 'test_field',
'type' => 'entity_reference',
'cardinality' => '1',
));
$field->save();
$instance = entity_create('field_instance', array(
'field_name' => 'test_field',
'entity_type' => 'test_entity',
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'default',
@ -351,19 +352,20 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
public function testCommentHandler() {
// Create a field and instance.
$field = entity_create('field_entity', array(
'name' => 'test_field',
'entity_type' => 'entity_test',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'comment',
),
'field_name' => 'test_field',
'type' => 'entity_reference',
'cardinality' => '1',
));
$field->save();
$instance = entity_create('field_instance', array(
'field_name' => 'test_field',
'entity_type' => 'test_entity',
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'default',

View File

@ -22,7 +22,7 @@ class EntityReferenceSelectionSortTest extends WebTestBase {
);
}
public static $modules = array('node', 'entity_reference');
public static $modules = array('node', 'entity_reference', 'entity_test');
function setUp() {
parent::setUp();
@ -37,7 +37,8 @@ class EntityReferenceSelectionSortTest extends WebTestBase {
public function testSort() {
// Add text field to entity, to sort by.
entity_create('field_entity', array(
'field_name' => 'field_text',
'name' => 'field_text',
'entity_type' => 'node',
'type' => 'text',
'entity_types' => array('node'),
))->save();
@ -54,19 +55,19 @@ class EntityReferenceSelectionSortTest extends WebTestBase {
// Create a field and instance.
$field = entity_create('field_entity', array(
'name' => 'test_field',
'entity_type' => 'entity_test',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'node',
),
'field_name' => 'test_field',
'type' => 'entity_reference',
'cardinality' => 1,
));
$field->save();
$instance = entity_create('field_instance', array(
'field_name' => 'test_field',
'entity_type' => 'test_entity',
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'default',

View File

@ -14,7 +14,7 @@ use Drupal\simpletest\WebTestBase;
*/
class SelectionTest extends WebTestBase {
public static $modules = array('views', 'entity_reference', 'entity_reference_test');
public static $modules = array('views', 'entity_reference', 'entity_reference_test', 'entity_test');
public static function getInfo() {
return array(
@ -41,19 +41,19 @@ class SelectionTest extends WebTestBase {
// Create a field and instance.
$field = entity_create('field_entity', array(
'name' => 'test_field',
'entity_type' => 'entity_test',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'node',
),
'field_name' => 'test_field',
'type' => 'entity_reference',
'cardinality' => '1',
));
$field->save();
$instance = entity_create('field_instance', array(
'field_name' => 'test_field',
'entity_type' => 'test_entity',
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'views',

View File

@ -1,3 +1,2 @@
default_storage: field_sql_storage
language_fallback: '1'
purge_batch_size: 10

View File

@ -4,9 +4,6 @@ field.settings:
type: mapping
label: 'Field settings'
mapping:
default_storage:
type: string
label: 'The default storage backend for a field'
language_fallback:
type: boolean
label: 'Whether the field display falls back to global language fallback configuration'
@ -30,6 +27,9 @@ field.field.*:
langcode:
type: string
label: 'Default language'
entity_type:
type: string
label: 'Entity type'
type:
type: string
label: 'Type'
@ -41,28 +41,6 @@ field.field.*:
active:
type: boolean
label: 'Active'
entity_types:
type: sequence
label: 'Allowed entity types'
sequence:
- type: string
label: 'Entity type'
storage:
type: mapping
label: 'Storage'
mapping:
type:
type: string
label: 'Type'
settings:
type: field_storage.[%parent.type].settings
label: 'Settings'
module:
type: string
label: 'Module'
active:
type: boolean
label: 'Active'
locked:
type: boolean
label: 'Locked'
@ -101,9 +79,6 @@ field.instance.*.*.*:
field_uuid:
type: string
label: 'Field UUID'
entity_type:
type: string
label: 'Allowed entity types'
bundle:
type: string
label: 'Bundle'

View File

@ -333,27 +333,6 @@ function hook_field_attach_form(\Drupal\Core\Entity\EntityInterface $entity, &$f
);
}
/**
* Act on field_attach_load().
*
* This hook is invoked after the field module has performed the operation.
*
* Unlike other field_attach hooks, this hook accounts for 'multiple loads'.
* Instead of the usual $entity parameter, it accepts an array of entities,
* indexed by entity ID. For performance reasons, information for all available
* entities should be loaded in a single query where possible.
*
* The changes made to the entities' field values get cached by the field cache
* for subsequent loads.
*
* See field_attach_load() for details and arguments.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function hook_field_attach_load($entity_type, $entities, $age, $options) {
// @todo Needs function body.
}
/**
* Act on field_attach_extract_form_values().
*
@ -399,31 +378,6 @@ function hook_field_attach_preprocess_alter(&$variables, $context) {
// @todo Needs function body.
}
/**
* Act on field_purge_data().
*
* This hook is invoked in field_purge_data() and allows modules to act on
* purging data from a single field pseudo-entity. For example, if a module
* relates data in the field with its own data, it may purge its own data during
* this process as well.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The pseudo-entity whose field data is being purged.
* @param $field
* The (possibly deleted) field whose data is being purged.
* @param $instance
* The deleted field instance whose data is being purged.
*
* @see @link field_purge Field API bulk data deletion @endlink
* @see field_purge_data()
*/
function hook_field_attach_purge(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance) {
// find the corresponding data in mymodule and purge it
if ($entity->entityType() == 'node' && $field->field_name == 'my_field_name') {
mymodule_remove_mydata($entity->id());
}
}
/**
* Perform alterations on field_attach_view() or field_view_field().
*
@ -511,681 +465,6 @@ function hook_field_available_languages_alter(&$langcodes, $context) {
* @} End of "addtogroup field_attach".
*/
/**
* @addtogroup field_storage
* @{
*/
/**
* Expose Field API storage backends.
*
* @return
* An array describing the storage backends implemented by the module. The
* keys are storage backend names. To avoid name clashes, storage backend
* names should be prefixed with the name of the module that exposes them. The
* values are arrays describing the storage backend, with the following
* key/value pairs:
* - label: The human-readable name of the storage backend.
* - description: A short description for the storage backend.
* - settings: An array whose keys are the names of the settings available to
* the storage backend, and whose values are the default values of those
* settings.
*/
function hook_field_storage_info() {
return array(
'field_sql_storage' => array(
'label' => t('Default SQL storage'),
'description' => t('Stores fields in the local SQL database, using per-field tables.'),
'settings' => array(),
),
);
}
/**
* Perform alterations on Field API storage types.
*
* @param $info
* Array of informations on storage types exposed by
* hook_field_field_storage_info() implementations.
*/
function hook_field_storage_info_alter(&$info) {
// Add a setting to a storage type.
$info['field_sql_storage']['settings'] += array(
'mymodule_additional_setting' => 'default value',
);
}
/**
* Reveal the internal details about the storage for a field.
*
* For example, an SQL storage module might return the Schema API structure for
* the table. A key/value storage module might return the server name,
* authentication credentials, and bin name.
*
* Field storage modules are not obligated to implement this hook. Modules that
* rely on these details must only use them for read operations.
*
* @param $field
* A field structure.
*
* @return
* An array of details.
* - The first dimension is a store type (sql, solr, etc).
* - The second dimension indicates the age of the values in the store
* FIELD_LOAD_CURRENT or FIELD_LOAD_REVISION.
* - Other dimensions are specific to the field storage module.
*
* @see hook_field_storage_details_alter()
*/
function hook_field_storage_details($field) {
$details = array();
// Add field columns.
foreach ((array) $field['columns'] as $column_name => $attributes) {
$real_name = _field_sql_storage_columnname($field['field_name'], $column_name);
$columns[$column_name] = $real_name;
}
return array(
'sql' => array(
FIELD_LOAD_CURRENT => array(
_field_sql_storage_tablename($field) => $columns,
),
FIELD_LOAD_REVISION => array(
_field_sql_storage_revision_tablename($field) => $columns,
),
),
);
}
/**
* Perform alterations on Field API storage details.
*
* @param $details
* An array of storage details for fields as exposed by
* hook_field_storage_details() implementations.
* @param $field
* A field structure.
*
* @see hook_field_storage_details()
*/
function hook_field_storage_details_alter(&$details, $field) {
if ($field['field_name'] == 'field_of_interest') {
$columns = array();
foreach ((array) $field['columns'] as $column_name => $attributes) {
$columns[$column_name] = $column_name;
}
$details['drupal_variables'] = array(
FIELD_LOAD_CURRENT => array(
'moon' => $columns,
),
FIELD_LOAD_REVISION => array(
'mars' => $columns,
),
);
}
}
/**
* Load field data for a set of entities.
*
* This hook is invoked from field_attach_load() to ask the field storage module
* to load field data.
*
* Modules implementing this hook should load field values and add them to
* objects in $entities. Fields with no values should be added as empty arrays.
*
* @param $entity_type
* The type of entity, such as 'node' or 'user'.
* @param $entities
* The array of entity objects to add fields to, keyed by entity ID.
* @param $age
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
* FIELD_LOAD_REVISION to load the version indicated by each entity.
* @param $fields
* An array listing the fields to be loaded. The keys of the array are field
* UUIDs, and the values of the array are the entity IDs (or revision IDs,
* depending on the $age parameter) to add each field to.
* @param $options
* An associative array of additional options, with the following keys:
* - deleted: If TRUE, deleted fields should be loaded as well as non-deleted
* fields. If unset or FALSE, only non-deleted fields should be loaded.
*/
function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) {
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
// By the time this hook runs, the relevant field definitions have been
// populated and cached in FieldInfo, so calling field_info_field_by_id()
// on each field individually is more efficient than loading all fields in
// memory upfront with field_info_field_by_ids().
$field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
$query = db_select($table, 't')
->fields('t')
->condition('entity_type', $entity_type)
->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
->condition('langcode', field_available_languages($entity_type, $field), 'IN')
->orderBy('delta');
if (empty($options['deleted'])) {
$query->condition('deleted', 0);
}
$results = $query->execute();
$delta_count = array();
foreach ($results as $row) {
if (!isset($delta_count[$row->entity_id][$row->langcode])) {
$delta_count[$row->entity_id][$row->langcode] = 0;
}
if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field['cardinality']) {
$item = array();
// For each column declared by the field, populate the item
// from the prefixed database column.
foreach ($field['columns'] as $column => $attributes) {
$column_name = _field_sql_storage_columnname($field_name, $column);
$item[$column] = $row->$column_name;
}
// Add the item to the field values for the entity.
$entities[$row->entity_id]->{$field_name}[$row->langcode][] = $item;
$delta_count[$row->entity_id][$row->langcode]++;
}
}
}
}
/**
* Write field data for an entity.
*
* This hook is invoked from field_attach_insert() and field_attach_update(), to
* ask the field storage module to save field data.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity on which to operate.
* @param $op
* FIELD_STORAGE_UPDATE when updating an existing entity,
* FIELD_STORAGE_INSERT when inserting a new entity.
* @param $fields
* An array listing the fields to be written. The keys and values of the
* array are field UUIDs.
*/
function hook_field_storage_write(\Drupal\Core\Entity\EntityInterface $entity, $op, $fields) {
$id = $entity->id();
$vid = $entity->getRevisionId();
$bundle = $entity->bundle();
if (!isset($vid)) {
$vid = $id;
}
foreach ($fields as $field_id) {
$field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
$all_langcodes = field_available_languages($entity->entityType(), $field);
$field_langcodes = array_intersect($all_langcodes, array_keys((array) $entity->$field_name));
// Delete and insert, rather than update, in case a value was added.
if ($op == FIELD_STORAGE_UPDATE) {
// Delete language codes present in the incoming $entity->$field_name.
// Delete all language codes if $entity->$field_name is empty.
$langcodes = !empty($entity->$field_name) ? $field_langcodes : $all_langcodes;
if ($langcodes) {
db_delete($table_name)
->condition('entity_type', $entity->entityType())
->condition('entity_id', $id)
->condition('langcode', $langcodes, 'IN')
->execute();
db_delete($revision_name)
->condition('entity_type', $entity->entityType())
->condition('entity_id', $id)
->condition('revision_id', $vid)
->condition('langcode', $langcodes, 'IN')
->execute();
}
}
// Prepare the multi-insert query.
$do_insert = FALSE;
$columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'langcode');
foreach ($field['columns'] as $column => $attributes) {
$columns[] = _field_sql_storage_columnname($field_name, $column);
}
$query = db_insert($table_name)->fields($columns);
$revision_query = db_insert($revision_name)->fields($columns);
foreach ($field_langcodes as $langcode) {
$items = (array) $entity->{$field_name}[$langcode];
$delta_count = 0;
foreach ($items as $delta => $item) {
// We now know we have someting to insert.
$do_insert = TRUE;
$record = array(
'entity_type' => $entity->entityType(),
'entity_id' => $id,
'revision_id' => $vid,
'bundle' => $bundle,
'delta' => $delta,
'langcode' => $langcode,
);
foreach ($field['columns'] as $column => $attributes) {
$record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
}
$query->values($record);
if (isset($vid)) {
$revision_query->values($record);
}
if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
break;
}
}
}
// Execute the query if we have values to insert.
if ($do_insert) {
$query->execute();
$revision_query->execute();
}
}
}
/**
* Delete all field data for an entity.
*
* This hook is invoked from field_attach_delete() to ask the field storage
* module to delete field data.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity on which to operate.
* @param $fields
* An array listing the fields to delete. The keys and values of the
* array are field UUIDs.
*/
function hook_field_storage_delete(\Drupal\Core\Entity\EntityInterface $entity, $fields) {
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
if (isset($fields[$instance['field_id']])) {
$field = field_info_field_by_id($instance['field_id']);
field_sql_storage_field_storage_purge($entity, $field, $instance);
}
}
}
/**
* Delete a single revision of field data for an entity.
*
* This hook is invoked from field_attach_delete_revision() to ask the field
* storage module to delete field revision data.
*
* Deleting the current (most recently written) revision is not
* allowed as has undefined results.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity on which to operate.
* @param $fields
* An array listing the fields to delete. The keys and values of the
* array are field UUIDs.
*/
function hook_field_storage_delete_revision(\Drupal\Core\Entity\EntityInterface $entity, $fields) {
$vid = $entity->getRevisionId();
if (isset($vid)) {
foreach ($fields as $field_id) {
$field = field_info_field_by_id($field_id);
$revision_name = _field_sql_storage_revision_tablename($field);
db_delete($revision_name)
->condition('entity_type', $entity_type)
->condition('entity_id', $entity->id())
->condition('revision_id', $vid)
->execute();
}
}
}
/**
* Execute a Drupal\Core\Entity\EntityFieldQuery.
*
* This hook is called to find the entities having certain entity and field
* conditions and sort them in the given field order. If the field storage
* engine also handles property sorts and orders, it should unset those
* properties in the called object to signal that those have been handled.
*
* @param Drupal\Core\Entity\EntityFieldQuery $query
* An EntityFieldQuery.
*
* @return
* See Drupal\Core\Entity\EntityFieldQuery::execute() for the return values.
*/
function hook_field_storage_query($query) {
$groups = array();
if ($query->age == FIELD_LOAD_CURRENT) {
$tablename_function = '_field_sql_storage_tablename';
$id_key = 'entity_id';
}
else {
$tablename_function = '_field_sql_storage_revision_tablename';
$id_key = 'revision_id';
}
$table_aliases = array();
// Add tables for the fields used.
foreach ($query->fields as $key => $field) {
$tablename = $tablename_function($field);
// Every field needs a new table.
$table_alias = $tablename . $key;
$table_aliases[$key] = $table_alias;
if ($key) {
$select_query->join($tablename, $table_alias, "$table_alias.entity_type = $field_base_table.entity_type AND $table_alias.$id_key = $field_base_table.$id_key");
}
else {
$select_query = db_select($tablename, $table_alias);
$select_query->addTag('entity_field_access');
$select_query->addMetaData('base_table', $tablename);
$select_query->fields($table_alias, array('entity_type', 'entity_id', 'revision_id', 'bundle'));
$field_base_table = $table_alias;
}
if ($field['cardinality'] != 1) {
$select_query->distinct();
}
}
// Add field conditions.
foreach ($query->fieldConditions as $key => $condition) {
$table_alias = $table_aliases[$key];
$field = $condition['field'];
// Add the specified condition.
$sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $condition['column']);
$query->addCondition($select_query, $sql_field, $condition);
// Add delta / language group conditions.
foreach (array('delta', 'langcode') as $column) {
if (isset($condition[$column . '_group'])) {
$group_name = $condition[$column . '_group'];
if (!isset($groups[$column][$group_name])) {
$groups[$column][$group_name] = $table_alias;
}
else {
$select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column");
}
}
}
}
if (isset($query->deleted)) {
$select_query->condition("$field_base_table.deleted", (int) $query->deleted);
}
// Is there a need to sort the query by property?
$has_property_order = FALSE;
foreach ($query->order as $order) {
if ($order['type'] == 'property') {
$has_property_order = TRUE;
}
}
if ($query->propertyConditions || $has_property_order) {
if (empty($query->entityConditions['entity_type']['value'])) {
throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.');
}
$entity_type = $query->entityConditions['entity_type']['value'];
$entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table);
$query->entityConditions['entity_type']['operator'] = '=';
foreach ($query->propertyConditions as $property_condition) {
$query->addCondition($select_query, "$entity_base_table." . $property_condition['column'], $property_condition);
}
}
foreach ($query->entityConditions as $key => $condition) {
$query->addCondition($select_query, "$field_base_table.$key", $condition);
}
// Order the query.
foreach ($query->order as $order) {
if ($order['type'] == 'entity') {
$key = $order['specifier'];
$select_query->orderBy("$field_base_table.$key", $order['direction']);
}
elseif ($order['type'] == 'field') {
$specifier = $order['specifier'];
$field = $specifier['field'];
$table_alias = $table_aliases[$specifier['index']];
$sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $specifier['column']);
$select_query->orderBy($sql_field, $order['direction']);
}
elseif ($order['type'] == 'property') {
$select_query->orderBy("$entity_base_table." . $order['specifier'], $order['direction']);
}
}
return $query->finishQuery($select_query, $id_key);
}
/**
* Act on creation of a new field.
*
* This hook is invoked during the creation of a field to ask the field storage
* module to save field information and prepare for storing field instances. If
* there is a problem, the field storage module should throw an exception.
*
* @param $field
* The field structure being created.
*/
function hook_field_storage_create_field($field) {
$schema = _field_sql_storage_schema($field);
foreach ($schema as $name => $table) {
db_create_table($name, $table);
}
drupal_get_schema(NULL, TRUE);
}
/**
* Update the storage information for a field.
*
* This is invoked on the field's storage module when updating the field,
* before the new definition is saved to the database. The field storage module
* should update its storage tables according to the new field definition. If
* there is a problem, the field storage module should throw an exception.
*
* @param $field
* The updated field structure to be saved.
* @param $prior_field
* The previously-saved field structure.
*/
function hook_field_storage_update_field($field, $prior_field) {
if (!$field->hasData()) {
// There is no data. Re-create the tables completely.
$prior_schema = _field_sql_storage_schema($prior_field);
foreach ($prior_schema as $name => $table) {
db_drop_table($name, $table);
}
$schema = _field_sql_storage_schema($field);
foreach ($schema as $name => $table) {
db_create_table($name, $table);
}
}
else {
// There is data. See field_sql_storage_field_storage_update_field() for
// an example of what to do to modify the schema in place, preserving the
// old data as much as possible.
}
drupal_get_schema(NULL, TRUE);
}
/**
* Act on deletion of a field.
*
* This hook is invoked during the deletion of a field to ask the field storage
* module to mark all information stored in the field for deletion.
*
* @param $field
* The field being deleted.
*/
function hook_field_storage_delete_field($field) {
// Mark all data associated with the field for deletion.
$field['deleted'] = FALSE;
$table = _field_sql_storage_tablename($field);
$revision_table = _field_sql_storage_revision_tablename($field);
db_update($table)
->fields(array('deleted' => 1))
->execute();
// Move the table to a unique name while the table contents are being deleted.
$field['deleted'] = TRUE;
$new_table = _field_sql_storage_tablename($field);
$revision_new_table = _field_sql_storage_revision_tablename($field);
db_rename_table($table, $new_table);
db_rename_table($revision_table, $revision_new_table);
drupal_get_schema(NULL, TRUE);
}
/**
* Act on deletion of a field instance.
*
* This hook is invoked during the deletion of a field instance to ask the
* field storage module to mark all information stored for the field instance
* for deletion.
*
* @param $instance
* The instance being deleted.
*/
function hook_field_storage_delete_instance($instance) {
$field = field_info_field($instance['field_name']);
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_update($table_name)
->fields(array('deleted' => 1))
->condition('entity_type', $instance['entity_type'])
->condition('bundle', $instance['bundle'])
->execute();
db_update($revision_name)
->fields(array('deleted' => 1))
->condition('entity_type', $instance['entity_type'])
->condition('bundle', $instance['bundle'])
->execute();
}
/**
* Act before the storage backends load field data.
*
* This hook allows modules to load data before the Field Storage API,
* optionally preventing the field storage module from doing so.
*
* This lets 3rd party modules override, mirror, share, or otherwise store a
* subset of fields in a different way than the current storage engine. Possible
* use cases include per-bundle storage, per-combo-field storage, etc.
*
* Modules implementing this hook should load field values and add them to
* objects in $entities. Fields with no values should be added as empty arrays.
* In addition, fields loaded should be added as keys to $skip_fields.
*
* @param $entity_type
* The type of entity, such as 'node' or 'user'.
* @param $entities
* The array of entity objects to add fields to, keyed by entity ID.
* @param $age
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
* FIELD_LOAD_REVISION to load the version indicated by each entity.
* @param $skip_fields
* An array keyed by field UUIDs whose data has already been loaded and
* therefore should not be loaded again. Add a key to this array to indicate
* that your module has already loaded a field.
* @param $options
* An associative array of additional options, with the following keys:
* - field_id: The field UUID that should be loaded. If unset, all fields
* should be loaded.
* - deleted: If TRUE, deleted fields should be loaded as well as non-deleted
* fields. If unset or FALSE, only non-deleted fields should be loaded.
*/
function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_fields, $options) {
// @todo Needs function body.
}
/**
* Act before the storage backends insert field data.
*
* This hook allows modules to store data before the Field Storage API,
* optionally preventing the field storage module from doing so.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity with fields to save.
* @param $skip_fields
* An array keyed by field UUIDs whose data has already been written and
* therefore should not be written again. The values associated with these
* keys are not specified.
* @return
* Saved field UUIDs are set as keys in $skip_fields.
*/
function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) {
if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
$query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
foreach ($entity->taxonomy_forums as $language) {
foreach ($language as $delta) {
$query->values(array(
'nid' => $entity->id(),
'title' => $entity->title,
'tid' => $delta['value'],
'sticky' => $entity->sticky,
'created' => $entity->created,
'comment_count' => 0,
'last_comment_timestamp' => $entity->created,
));
}
}
$query->execute();
}
}
/**
* Act before the storage backends update field data.
*
* This hook allows modules to store data before the Field Storage API,
* optionally preventing the field storage module from doing so.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity with fields to save.
* @param $skip_fields
* An array keyed by field UUIDs whose data has already been written and
* therefore should not be written again. The values associated with these
* keys are not specified.
* @return
* Saved field UUIDs are set as keys in $skip_fields.
*/
function hook_field_storage_pre_update(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) {
$first_call = &drupal_static(__FUNCTION__, array());
if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
// We don't maintain data for old revisions, so clear all previous values
// from the table. Since this hook runs once per field, per entity, make
// sure we only wipe values once.
if (!isset($first_call[$entity->id()])) {
$first_call[$entity->id()] = FALSE;
db_delete('forum_index')->condition('nid', $entity->id())->execute();
}
// Only save data to the table if the node is published.
if ($entity->status) {
$query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
foreach ($entity->taxonomy_forums as $language) {
foreach ($language as $delta) {
$query->values(array(
'nid' => $entity->id(),
'title' => $entity->title,
'tid' => $delta['value'],
'sticky' => $entity->sticky,
'created' => $entity->created,
'comment_count' => 0,
'last_comment_timestamp' => $entity->created,
));
}
}
$query->execute();
// The logic for determining last_comment_count is fairly complex, so
// call _forum_update_forum_index() too.
_forum_update_forum_index($entity->id());
}
}
}
/**
* Returns the maximum weight for the entity components handled by the module.
*
@ -1216,10 +495,6 @@ function hook_field_info_max_weight($entity_type, $bundle, $context, $context_mo
return $weights ? max($weights) : NULL;
}
/**
* @} End of "addtogroup field_storage".
*/
/**
* @addtogroup field_crud
* @{
@ -1299,63 +574,6 @@ function hook_field_purge_instance($instance) {
->execute();
}
/**
* Remove field storage information when a field record is purged.
*
* Called from field_purge_field() to allow the field storage module to remove
* field information when a field is being purged.
*
* @param $field
* The field being purged.
*/
function hook_field_storage_purge_field($field) {
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_drop_table($table_name);
db_drop_table($revision_name);
}
/**
* Remove field storage information when a field instance is purged.
*
* Called from field_purge_instance() to allow the field storage module to
* remove field instance information when a field instance is being purged.
*
* @param $instance
* The instance being purged.
*/
function hook_field_storage_purge_field_instance($instance) {
db_delete('my_module_field_instance_info')
->condition('id', $instance['id'])
->execute();
}
/**
* Remove field storage information when field data is purged.
*
* Called from field_purge_data() to allow the field storage module to delete
* field data information.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The pseudo-entity whose field data to delete.
* @param $field
* The (possibly deleted) field whose data is being purged.
* @param $instance
* The deleted field instance whose data is being purged.
*/
function hook_field_storage_purge(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance) {
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_delete($table_name)
->condition('entity_type', $entity->entityType())
->condition('entity_id', $entity->id())
->execute();
db_delete($revision_name)
->condition('entity_type', $entity->entityType())
->condition('entity_id', $entity->id())
->execute();
}
/**
* @} End of "addtogroup field_crud".
*/

View File

@ -8,45 +8,6 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\Entity\EntityFormDisplay;
/**
* @defgroup field_storage Field Storage API
* @{
* Implements a storage engine for Field API data.
*
* The Field Attach API uses the Field Storage API to perform all "database
* access". Each Field Storage API hook function defines a primitive database
* operation such as read, write, or delete. The default field storage module,
* field_sql_storage.module, uses the local SQL database to implement these
* operations, but alternative field storage backends can choose to represent
* the data in SQL differently or use a completely different storage mechanism
* such as a cloud-based database.
*
* Each field defines which storage backend it uses. The Drupal configuration
* 'field.settings.default_storage' identifies the storage backend used by
* default.
*
* See @link field Field API @endlink for information about the other parts of
* the Field API.
*/
/**
* Argument for an update operation.
*
* This is used in hook_field_storage_write when updating an existing entity.
*/
const FIELD_STORAGE_UPDATE = 'update';
/**
* Argument for an insert operation.
*
* This is used in hook_field_storage_write when inserting a new entity.
*/
const FIELD_STORAGE_INSERT = 'insert';
/**
* @} End of "defgroup field_storage".
*/
/**
* @defgroup field_attach Field Attach API
* @{
@ -166,7 +127,7 @@ function field_invoke_method($method, $target_function, EntityInterface $entity,
$target = call_user_func($target_function, $instance);
if (method_exists($target, $method)) {
$field = field_info_field_by_id($instance['field_id']);
$field = $instance->getField();
$field_name = $field['field_name'];
// Determine the list of languages to iterate on.
@ -273,7 +234,7 @@ function field_invoke_method_multiple($method, $target_function, array $entities
// Unless a language code suggestion is provided we iterate on all the
// available language codes.
$field = field_info_field_by_id($instance['field_id']);
$field = $instance->getField();
$available_langcodes = field_available_languages($entity_type, $field);
$langcode = !empty($options['langcode'][$id]) ? $options['langcode'][$id] : $options['langcode'];
$langcodes = _field_language_suggestion($available_langcodes, $langcode, $field_name);

View File

@ -164,18 +164,20 @@ function field_info_formatter_settings($type) {
* The function only returns active, non deleted fields.
*
* @return
* An array keyed by field name. Each value is an array with two entries:
* An array keyed by entity type. Each value is an array which keys are
* field names and value is an array with two entries:
* - type: The field type.
* - bundles: The bundles in which the field appears, as an array with entity
* types as keys and the array of bundle names as values.
* - bundles: The bundles in which the field appears.
* Example:
* @code
* array(
* 'body' => array(
* 'bundles' => array(
* 'node' => array('page', 'article'),
* 'node' => array(
* 'body' => array(
* 'bundles' => array(
* 'page', 'article'
* ),
* 'type' => 'text_with_summary',
* ),
* 'type' => 'text_with_summary',
* ),
* );
* @endcode
@ -188,8 +190,10 @@ function field_info_field_map() {
}
/**
* Returns data about an individual field, given a field name.
* Returns data about an individual field.
*
* @param $entity_type
* The entity type.
* @param $field_name
* The name of the field to retrieve. $field_name can only refer to a
* non-deleted, active field. For deleted fields, use
@ -207,8 +211,8 @@ function field_info_field_map() {
* @deprecated as of Drupal 8.0. Use
* Field::fieldInfo()->getField($field_name).
*/
function field_info_field($field_name) {
return Field::fieldInfo()->getField($field_name);
function field_info_field($entity_type, $field_name) {
return Field::fieldInfo()->getField($entity_type, $field_name);
}
/**
@ -334,6 +338,8 @@ function field_info_instance($entity_type, $field_name, $bundle_name) {
* This function will not return deleted fields. Use field_read_fields() instead
* for this purpose.
*
* @param $entity_type
* The entity type.
* @param $field_name
* The field name to read.
* @param array $include_additional
@ -347,8 +353,8 @@ function field_info_instance($entity_type, $field_name, $bundle_name) {
* @deprecated as of Drupal 8.0. Use
* entity_load('field_entity', 'field_name').
*/
function field_read_field($field_name, $include_additional = array()) {
$fields = field_read_fields(array('field_name' => $field_name), $include_additional);
function field_read_field($entity_type, $field_name, $include_additional = array()) {
$fields = field_read_fields(array('entity_type' => $entity_type, 'name' => $field_name), $include_additional);
return $fields ? current($fields) : FALSE;
}
@ -582,215 +588,6 @@ function field_attach_form(EntityInterface $entity, &$form, &$form_state, $langc
}
}
/**
* Loads fields for the current revisions of a group of entities.
*
* Loads all fields for each entity object in a group of a single entity type.
* The loaded field values are added directly to the entity objects.
*
* field_attach_load() is automatically called by the default entity controller
* class, and thus, in most cases, doesn't need to be explicitly called by the
* entity type module.
*
* @param $entity_type
* The type of entities in $entities; e.g., 'node' or 'user'.
* @param $entities
* An array of entities for which to load fields, keyed by entity ID. Each
* entity needs to have its 'bundle', 'id' and (if applicable) 'revision' keys
* filled in. The function adds the loaded field data directly in the entity
* objects of the $entities array.
* @param $age
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
* FIELD_LOAD_REVISION to load the version indicated by each entity. Defaults
* to FIELD_LOAD_CURRENT; use field_attach_load_revision() instead of passing
* FIELD_LOAD_REVISION.
* @param $options
* An associative array of additional options, with the following keys:
* - instance: A field instance entity, If provided, only values for the
* corresponding field will be loaded, and no cache is written. This
* option is only supported when all $entities are within the same bundle.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
$load_current = $age == FIELD_LOAD_CURRENT;
$load_deleted = !empty($options['instance']->deleted);
// Merge default options.
$options += array('instance' => NULL);
// Set options for hook invocations.
$hook_options = array(
'deleted' => $load_deleted,
);
if ($options['instance']) {
$hook_options['field_id'] = $options['instance']->field_uuid;
}
$info = entity_get_info($entity_type);
// Only the most current revision of non-deleted fields for cacheable entity
// types can be cached.
$cache_read = $load_current && $info['field_cache'] && !$load_deleted;
// In addition, do not write to the cache when loading a single field.
$cache_write = $cache_read && !isset($options['instance']);
if (empty($entities)) {
return;
}
// Ensure we are working with a BC mode entity.
foreach ($entities as $id => $entity) {
$entities[$id] = $entity->getBCEntity();
}
// Assume all entities will need to be queried. Entities found in the cache
// will be removed from the list.
$queried_entities = $entities;
// Fetch available entities from cache, if applicable.
if ($cache_read) {
// Build the list of cache entries to retrieve.
$cids = array();
foreach ($entities as $id => $entity) {
$cids[] = "field:$entity_type:$id";
}
$cache = cache('field')->getMultiple($cids);
// Put the cached field values back into the entities and remove them from
// the list of entities to query.
foreach ($entities as $id => $entity) {
$cid = "field:$entity_type:$id";
if (isset($cache[$cid])) {
unset($queried_entities[$id]);
foreach ($cache[$cid]->data as $field_name => $values) {
$entity->$field_name = $values;
}
}
}
}
// Fetch other entities from their storage location.
if ($queried_entities) {
// The invoke order is:
// - hook_field_storage_pre_load()
// - storage backend's hook_field_storage_load()
// - Field class's prepareCache() method.
// - hook_field_attach_load()
// Invoke hook_field_storage_pre_load(): let any module load field
// data before the storage engine, accumulating along the way.
$skip_fields = array();
foreach (Drupal::moduleHandler()->getImplementations('field_storage_pre_load') as $module) {
$function = $module . '_field_storage_pre_load';
$function($entity_type, $queried_entities, $age, $skip_fields, $hook_options);
}
// Collect the storage backends used by the remaining fields in the entities.
$storages = array();
foreach ($queried_entities as $entity) {
$id = $entity->id();
$vid = $entity->getRevisionId();
// Determine the list of field instances to work on.
if ($options['instance']) {
$instances = array($options['instance']);
}
else {
$instances = field_info_instances($entity_type, $entity->bundle());
}
foreach ($instances as $instance) {
$field = $instance->getField();
$field_name = $field->id();
if (!isset($queried_entities[$id]->{$field_name})) {
$queried_entities[$id]->{$field_name} = array();
}
if (!isset($skip_fields[$field->uuid])) {
$storages[$field->storage['type']][$field->uuid][] = $load_current ? $id : $vid;
}
}
}
// Invoke hook_field_storage_load() on the relevant storage backends.
foreach ($storages as $storage => $fields) {
$storage_info = field_info_storage_types($storage);
module_invoke($storage_info['module'], 'field_storage_load', $entity_type, $queried_entities, $age, $fields, $hook_options);
}
// Invoke the field type's prepareCache() method.
if (empty($options['instance'])) {
foreach ($queried_entities as $entity) {
\Drupal::entityManager()
->getStorageController($entity_type)
->invokeFieldItemPrepareCache($entity);
}
}
else {
// Do not rely on invokeFieldItemPrepareCache(), which only works on
// fields listed in getFieldDefinitions(), and will fail if we are loading
// values for a deleted field. Instead, generate FieldItem objects
// directly, and call their prepareCache() method.
foreach ($queried_entities as $entity) {
$field = $options['instance']->getField();
$field_name = $field->id();
// Call the prepareCache() method on each item.
foreach ($entity->{$field_name} as $langcode => $values) {
$definition = _field_generate_entity_field_definition($field, $options['instance']);
$items = \Drupal::typedData()->create($definition, $values, $field_name, $entity);
foreach ($items as $item) {
if ($item instanceof PrepareCacheInterface) {
$item->prepareCache();
}
}
$entity->{$field_name}[$langcode] = $items->getValue();
}
}
}
// Invoke hook_field_attach_load(): let other modules act on loading the
// entity.
Drupal::moduleHandler()->invokeAll('field_attach_load', $entity_type, $queried_entities, $age, $options);
// Build cache data.
if ($cache_write) {
foreach ($queried_entities as $id => $entity) {
$data = array();
$instances = field_info_instances($entity_type, $entity->bundle());
foreach ($instances as $instance) {
$data[$instance['field_name']] = $queried_entities[$id]->{$instance['field_name']};
}
$cid = "field:$entity_type:$id";
cache('field')->set($cid, $data);
}
}
}
}
/**
* Loads all fields for previous versions of a group of entities.
*
* Loading different versions of the same entities is not supported, and should
* be done by separate calls to the function.
*
* field_attach_load_revision() is automatically called by the default entity
* controller class, and thus, in most cases, doesn't need to be explicitly
* called by the entity type module.
*
* @param $entity_type
* The type of entities in $entities; e.g. 'node' or 'user'.
* @param $entities
* An array of entities for which to load fields, keyed by entity ID. Each
* entity needs to have its 'bundle', 'id' and (if applicable) 'revision' keys
* filled. The function adds the loaded field data directly in the entity
* objects of the $entities array.
* @param $options
* An associative array of additional options. See field_attach_load() for
* details.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function field_attach_load_revision($entity_type, $entities, $options = array()) {
return field_attach_load($entity_type, $entities, FIELD_LOAD_REVISION, $options);
}
/**
* Performs field validation against form-submitted field values.
*
@ -837,7 +634,7 @@ function field_attach_form_validate(EntityInterface $entity, $form, &$form_state
$has_violations = TRUE;
// Place violations in $form_state.
$langcode = field_is_translatable($entity->entityType(), field_info_field($field_name)) ? $entity->language()->id : Language::LANGCODE_NOT_SPECIFIED;
$langcode = field_is_translatable($entity->entityType(), field_info_field($entity->entityType(), $field_name)) ? $entity->language()->id : Language::LANGCODE_NOT_SPECIFIED;
$field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
$field_state['constraint_violations'] = $field_violations;
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
@ -888,166 +685,13 @@ function field_attach_extract_form_values(EntityInterface $entity, $form, &$form
}
}
/**
* Save field data for a new entity.
*
* The passed-in entity must already contain its id and (if applicable)
* revision id attributes.
* Default values (if any) will be saved for fields not present in the
* $entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity with fields to save.
* @return
* Default values (if any) will be added to the $entity parameter for fields
* it leaves unspecified.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function field_attach_insert(EntityInterface $entity) {
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
// Let any module insert field data before the storage engine, accumulating
// saved fields along the way.
$skip_fields = array();
foreach (Drupal::moduleHandler()->getImplementations('field_storage_pre_insert') as $module) {
$function = $module . '_field_storage_pre_insert';
$function($entity, $skip_fields);
}
// Collect the storage backends used by the remaining fields in the entities.
$storages = array();
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field = field_info_field_by_id($instance['field_id']);
$field_id = $field['uuid'];
$field_name = $field['field_name'];
if (!empty($entity->$field_name)) {
// Collect the storage backend if the field has not been written yet.
if (!isset($skip_fields[$field_id])) {
$storages[$field['storage']['type']][$field_id] = $field_id;
}
}
}
// Field storage backends save any remaining unsaved fields.
foreach ($storages as $storage => $fields) {
$storage_info = field_info_storage_types($storage);
module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_INSERT, $fields);
}
}
/**
* Saves field data for an existing entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity with fields to save.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function field_attach_update(EntityInterface $entity) {
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
// Let any module update field data before the storage engine, accumulating
// saved fields along the way.
$skip_fields = array();
foreach (Drupal::moduleHandler()->getImplementations('field_storage_pre_update') as $module) {
$function = $module . '_field_storage_pre_update';
$function($entity, $skip_fields);
}
// Collect the storage backends used by the remaining fields in the entities.
$storages = array();
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field = field_info_field_by_id($instance['field_id']);
$field_id = $field['uuid'];
// Collect the storage backend if the field has not been written yet.
if (!isset($skip_fields[$field_id])) {
$storages[$field['storage']['type']][$field_id] = $field_id;
}
}
// Field storage backends save any remaining unsaved fields.
foreach ($storages as $storage => $fields) {
$storage_info = field_info_storage_types($storage);
module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_UPDATE, $fields);
}
$entity_info = $entity->entityInfo();
if ($entity_info['field_cache']) {
cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
}
}
/**
* Deletes field data for an existing entity. This deletes all revisions of
* field data for the entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose field data to delete.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function field_attach_delete(EntityInterface $entity) {
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
// Collect the storage backends used by the fields in the entities.
$storages = array();
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field = field_info_field_by_id($instance['field_id']);
$field_id = $field['uuid'];
$storages[$field['storage']['type']][$field_id] = $field_id;
}
// Field storage backends delete their data.
foreach ($storages as $storage => $fields) {
$storage_info = field_info_storage_types($storage);
module_invoke($storage_info['module'], 'field_storage_delete', $entity, $fields);
}
$entity_info = $entity->entityInfo();
if ($entity_info['field_cache']) {
cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
}
}
/**
* Delete field data for a single revision of an existing entity. The passed
* entity must have a revision ID attribute.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity with fields to save.
*
* @deprecated as of Drupal 8.0. Use the entity system instead.
*/
function field_attach_delete_revision(EntityInterface $entity) {
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
// Collect the storage backends used by the fields in the entities.
$storages = array();
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
$field = field_info_field_by_id($instance['field_id']);
$field_id = $field['uuid'];
$storages[$field['storage']['type']][$field_id] = $field_id;
}
// Field storage backends delete their data.
foreach ($storages as $storage => $fields) {
$storage_info = field_info_storage_types($storage);
module_invoke($storage_info['module'], 'field_storage_delete_revision', $entity, $fields);
}
}
/**
* Prepares field data prior to display.
*
* This function lets field types and formatters load additional data needed for
* display that is not automatically loaded during field_attach_load(). It
* accepts an array of entities to allow query optimization when displaying
* lists of entities.
* display that is not automatically loaded during entity loading. It accepts an
* array of entities to allow query optimization when displaying lists of
* entities.
*
* field_attach_prepare_view() and field_attach_view() are two halves of the
* same operation. It is safe to call field_attach_prepare_view() multiple times

View File

@ -41,76 +41,9 @@ function field_info_cache_clear() {
Drupal::typedData()->clearCachedDefinitions();
Drupal::service('plugin.manager.entity.field.field_type')->clearCachedDefinitions();
_field_info_collate_types_reset();
Field::fieldInfo()->flush();
}
/**
* Collates all information on field types, widget types and related structures.
*
* @return
* An associative array containing:
* - 'storage types': Array of hook_field_storage_info() results, keyed by
* storage type names. Each element has the following components: label,
* description, and settings from hook_field_storage_info(), as well as
* module, giving the module that exposes the storage type.
*
* @see _field_info_collate_types_reset()
*/
function _field_info_collate_types() {
$language_interface = language(Language::TYPE_INTERFACE);
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['field_info_collate_types'] = &drupal_static(__FUNCTION__);
}
$info = &$drupal_static_fast['field_info_collate_types'];
// The _info() hooks invoked below include translated strings, so each
// language is cached separately.
$langcode = $language_interface->id;
if (!isset($info)) {
if ($cached = cache('field')->get("field_info_types:$langcode")) {
$info = $cached->data;
}
else {
$info = array(
'storage types' => array(),
);
// Populate storage types.
foreach (Drupal::moduleHandler()->getImplementations('field_storage_info') as $module) {
$storage_types = (array) module_invoke($module, 'field_storage_info');
foreach ($storage_types as $name => $storage_info) {
// Provide defaults.
$storage_info += array(
'settings' => array(),
);
$info['storage types'][$name] = $storage_info;
$info['storage types'][$name]['module'] = $module;
}
}
drupal_alter('field_storage_info', $info['storage types']);
cache('field')->set("field_info_types:$langcode", $info, CacheBackendInterface::CACHE_PERMANENT, array('field_info_types' => TRUE));
}
}
return $info;
}
/**
* Clears collated information on field and widget types and related structures.
*/
function _field_info_collate_types_reset() {
drupal_static_reset('_field_info_collate_types');
// Clear all languages.
cache('field')->deleteTags(array('field_info_types' => TRUE));
}
/**
* Determines the behavior of a widget with respect to an operation.
*
@ -134,31 +67,6 @@ function field_behaviors_widget($op, $instance) {
return isset($info[$op]) ? $info[$op] : FIELD_BEHAVIOR_DEFAULT;
}
/**
* Returns information about field storage from hook_field_storage_info().
*
* @param $storage_type
* (optional) A storage type name. If omitted, all storage types will be
* returned.
*
* @return
* Either a storage type description, as provided by
* hook_field_storage_info(), or an array of all existing storage types, keyed
* by storage type name.
*/
function field_info_storage_types($storage_type = NULL) {
$info = _field_info_collate_types();
$storage_types = $info['storage types'];
if ($storage_type) {
if (isset($storage_types[$storage_type])) {
return $storage_types[$storage_type];
}
}
else {
return $storage_types;
}
}
/**
* Returns all field definitions.
*
@ -171,27 +79,17 @@ function field_info_storage_types($storage_type = NULL) {
* field_info_field() on each individual field instead.
*
* @return
* An array of field definitions, keyed by field name. Each field has an
* additional property, 'bundles', which is an array of all the bundles to
* which this field belongs, keyed by entity type.
* An array of field definitions, keyed by field UUID.
*
* @see field_info_field_map()
*/
function field_info_fields() {
$info = Field::fieldInfo()->getFields();
$fields = array();
foreach ($info as $key => $field) {
if (!$field['deleted']) {
$fields[$field['field_name']] = $field;
}
}
return $fields;
// Filter out deleted fields.
return array_filter(Field::fieldInfo()->getFields(), function ($field) {
return !$field->deleted;
});
}
/**
* Returns a list and settings of pseudo-field elements in a given bundle.
*
@ -251,22 +149,6 @@ function field_info_extra_fields($entity_type, $bundle, $context) {
return isset($info[$context]) ? $info[$context] : array();
}
/**
* Returns a field storage type's default settings.
*
* @param $type
* A field storage type name.
*
* @return
* The storage type's default settings, as provided by
* hook_field_storage_info(), or an empty array if type or settings are
* undefined.
*/
function field_info_storage_settings($type) {
$info = field_info_storage_types($type);
return isset($info['settings']) ? $info['settings'] : array();
}
/**
* @} End of "defgroup field_info".
*/

View File

@ -6,6 +6,7 @@
*/
use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\field\Entity\Field;
/**
@ -29,8 +30,8 @@ function _update_8003_field_create_field(array &$field_config) {
// Merge in default values.
$field_config += array(
'id' => $field_config['entity_type'] . '.' . $field_config['name'],
'uuid' => $uuid->generate(),
'entity_types' => array(),
'cardinality' => 1,
'translatable' => FALSE,
'locked' => FALSE,
@ -41,14 +42,6 @@ function _update_8003_field_create_field(array &$field_config) {
'langcode' => 'und',
);
// Set the storage.
$field_config['storage'] = array(
'type' => 'field_sql_storage',
'module' => 'field_sql_storage',
'active' => TRUE,
'settings' => array(),
);
// Save in config.
Drupal::config('field.field.' . $field_config['id'])
->setData($field_config)
@ -56,15 +49,17 @@ function _update_8003_field_create_field(array &$field_config) {
// Create storage for the field. This requires a field entity, but cannot use
// the regular entity_create() function here.
$field_entity = new Field($field_config);
field_sql_storage_field_storage_create_field($field_entity);
$schema = DatabaseStorageController::_fieldSqlSchema(new Field($field_config), $field_config['schema']);
foreach ($schema as $name => $table) {
db_create_table($name, $table);
}
}
/**
* Writes a field instance directly to configuration.
*
* Upgrades using this function need to use hook_update_dependencies() to ensure
* they get executed after field_update_8003().
* they get executed after field_update_8003().
*
* @param array $field_config
* An array of field properties.
@ -78,6 +73,7 @@ function _update_8003_field_create_instance(array $field_config, array &$instanc
// Merge in defaults.
$instance_config += array(
'id' => $instance_config['entity_type'] . '.' . $instance_config['bundle'] . '.' . $field_config['name'],
'description' => '',
'required' => FALSE,
'uuid' => $uuid->generate(),
@ -97,6 +93,75 @@ function _update_8003_field_create_instance(array $field_config, array &$instanc
->save();
}
/**
* Writes field data directly to SQL storage.
*
* @param string $entity_type
* The entity type.
* @param string $bundle
* The bundle.
* @param int $entity_id
* The entity ID.
* @param int $revision_id
* The entity revision ID.
* @param string $field_name
* The field name.
* @param array $data
* The field values, as an array keyed by langcode, delta and property name.
*
* @ingroup update_api
*/
function _update_8006_field_write_data_sql($entity_type, $bundle, $entity_id, $revision_id, $field_name, array $data) {
if (!isset($revision_id)) {
$revision_id = $entity_id;
}
$field_config = Drupal::config("field.field.$entity_type.$field_name");
$field = new Field($field_config);
$table_name = DatabaseStorageController::_fieldTableName($field);
$revision_name = DatabaseStorageController::_fieldRevisionTableName($field);
db_delete($table_name)
->condition('entity_id', $entity_id)
->execute();
db_delete($revision_name)
->condition('entity_id', $entity_id)
->condition('revision_id', $revision_id)
->execute();
$columns = array();
foreach ($data as $langcode => $items) {
foreach ($items as $delta => $item) {
$record = array(
'entity_id' => $entity_id,
'revision_id' => $revision_id,
'bundle' => $bundle,
'delta' => $delta,
'langcode' => $langcode,
);
foreach ($item as $column => $value) {
$record[DatabaseStorageController::_fieldColumnName($field_name, $column)] = $value;
}
$records[] = $record;
// Record the columns used.
$columns += $record;
}
}
if ($columns) {
$query = db_insert($table_name)->fields(array_keys($columns));
$revision_query = db_insert($revision_name)->fields(array_keys($columns));
foreach ($records as $record) {
$query->values($record);
$revision_query->values($record);
}
$query->execute();
$revision_query->execute();
}
}
/**
* @addtogroup updates-7.x-to-8.x
* @{
@ -255,27 +320,22 @@ function field_update_8003() {
$field_data = array();
// Migrate field definitions.
$records = db_query("SELECT * FROM {field_config}")->fetchAll(PDO::FETCH_ASSOC);
$records = db_query("SELECT DISTINCT entity_type, fc.* FROM {field_config} fc INNER JOIN {field_config_instance} fci ON fc.id = fci.field_id")->fetchAll(PDO::FETCH_ASSOC);
foreach ($records as $record) {
$record['data'] = unserialize($record['data']);
$config = array(
'id' => $record['field_name'],
'id' => $record['entity_type'] . '.' . $record['field_name'],
'name' => $record['field_name'],
'uuid' => $uuid->generate(),
'type' => $record['type'],
'module' => $record['module'],
'active' => $record['active'],
'entity_type' => $record['entity_type'],
'settings' => $record['data']['settings'],
'storage' => array(
'type' => $record['storage_type'],
'module' => $record['storage_module'],
'active' => $record['storage_active'],
'settings' => $record['data']['storage']['settings'],
),
'locked' => $record['locked'],
'cardinality' => $record['cardinality'],
'translatable' => $record['translatable'],
'entity_types' => $record['data']['entity_types'],
'indexes' => $record['data']['indexes'] ?: array(),
'status' => 1,
'langcode' => 'und',
@ -295,19 +355,17 @@ function field_update_8003() {
else {
$config['deleted'] = TRUE;
$deleted_fields[$config['uuid']] = $config;
// Additionally, rename the data tables for deleted fields. Technically
// this would belong in an update in field_sql_storage.module, but it is
// easier to do it now, when the old numeric ID is available.
if ($config['storage']['type'] == 'field_sql_storage') {
$field = new Field($config);
$tables = array(
"field_deleted_data_{$record['id']}" => _field_sql_storage_tablename($field),
"field_deleted_revision_{$record['id']}" => _field_sql_storage_revision_tablename($field),
);
foreach ($tables as $table_old => $table_new) {
if (db_table_exists($table_old)) {
db_rename_table($table_old, $table_new);
}
// This will not be saved but the DatabaseStorageController helpers need
// the field object.
$field = new Field($config);
// Additionally, rename the data tables for deleted fields.
$tables = array(
"field_deleted_data_{$record['id']}" => 'old_' . DatabaseStorageController::_fieldTableName($field),
"field_deleted_revision_{$record['id']}" => 'old_' . DatabaseStorageController::_fieldRevisionTableName($field),
);
foreach ($tables as $table_old => $table_new) {
if (db_table_exists($table_old)) {
db_rename_table($table_old, $table_new);
}
}
}
@ -371,14 +429,13 @@ function field_update_8003() {
}
/**
* Moves field_storage_default and field_language_fallback to config.
* Moves field_language_fallback to config.
*
* @ingroup config_upgrade
*/
function field_update_8004() {
update_variable_set('field_language_fallback', TRUE);
update_variables_to_config('field.settings', array(
'field_storage_default' => 'default_storage',
'field_language_fallback' => 'language_fallback',
));
}
@ -416,6 +473,106 @@ function field_update_8005() {
->save();
}
/**
* Splits the field storage tables by entity type and also migrate langcode.
*/
function field_update_8006(&$sandbox) {
// Get field definitions from config, and deleted fields from state system.
$config_names = config_get_storage_names_with_prefix('field.field');
$deleted_fields = Drupal::state()->get('field.field.deleted') ?: array();
// Ditch UUID keys, we will iterate through deleted fields using a numeric
// index.
$deleted_fields = array_values($deleted_fields);
if (empty($config_names) && empty($deleted_fields)) {
return;
}
if (!isset($sandbox['index'])) {
$sandbox['index'] = 0;
$sandbox['max'] = count($config_names) + count($deleted_fields);
}
// Retrieve the next field definition. When the index exceeds the number of
// 'configuration' fields, use it to iterate on deleted fields.
if (isset($config_names[$sandbox['index']])) {
$field_config = Drupal::config($config_names[$sandbox['index']])->get();
}
else {
$field_config = $deleted_fields[$sandbox['index'] - count($config_names)];
}
// Prepare updated schema data structures.
$field = new Field($field_config);
$tables = array(
array(
'old_table' => 'field_data_' . $field_config['name'],
'new_table' => DatabaseStorageController::_fieldTableName($field),
'primary_key' => array(
'entity_id',
'deleted',
'delta',
'langcode',
),
),
array(
'old_table' => 'field_revision_' . $field_config['name'],
'new_table' => DatabaseStorageController::_fieldRevisionTableName($field),
'primary_key' => array(
'entity_id',
'revision_id',
'deleted',
'delta',
'langcode',
),
),
);
// Move the field data to the new table.
foreach ($tables as $table_data) {
// Split data from the old "per field" table to the new "per entity type and
// field" table.
$new_table = $table_data['new_table'];
$original_table = empty($field_config['deleted']) ? $table_data['old_table'] : 'old_' . $new_table;
if (db_table_exists($original_table)) {
// Create the new table, with the same schema as the old one.
if (!db_table_exists($new_table)) {
db_copy_table_schema($original_table, $new_table);
}
// Copy relevant rows.
$from_query = db_select($original_table, 'original')
->fields('original')
->condition('entity_type', $field_config['entity_type']);
db_insert($new_table)
->from($from_query)
->execute();
// Drop the 'entity_type' column and previous primary key.
db_drop_primary_key($new_table);
db_drop_field($new_table, 'entity_type');
// Rename 'language' to 'langcode'. Tables created during the upgrade
// before this update function might already have the langcode column.
if (db_field_exists($new_table, 'language')) {
db_drop_index($new_table, 'language');
db_change_field($new_table, 'language', 'langcode', array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
));
db_add_index($new_table, 'langcode', array('langcode'));
}
// Create new primary key.
db_add_primary_key($new_table, $table_data['primary_key']);
}
}
$sandbox['index']++;
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['index'] / $sandbox['max']);
}
/**
* @} End of "addtogroup updates-7.x-to-8.x".
* The next series of updates should start at 9000.

View File

@ -46,9 +46,7 @@ require_once __DIR__ . '/field.deprecated.inc';
* type 'image'. The administrator (again, via a UI) creates two Field
* Instances, one attaching the field 'subtitle' to the 'node' bundle 'article'
* and one attaching the field 'photo' to the 'node' bundle 'article'. When the
* node system uses the Field Attach API to load all fields for an Article node,
* it passes the node's entity type (which is 'node') and content type (which is
* 'article') as the node's bundle. field_attach_load() then loads the
* node storage controller loads an Article node, it loads the values of the
* 'subtitle' and 'photo' fields because they are both attached to the 'node'
* bundle 'article'.
*
@ -272,7 +270,7 @@ function field_entity_field_info($entity_type) {
function _field_generate_entity_field_definition(FieldInterface $field, FieldInstanceInterface $instance = NULL) {
// @todo: Allow for adding field type settings.
$definition = array(
'label' => t('Field !name', array('!name' => $field->id())),
'label' => t('Field !name', array('!name' => $field->name)),
'type' => 'field_item:' . $field->type,
'list' => TRUE,
'configurable' => TRUE,
@ -399,24 +397,8 @@ function field_sync_field_status() {
// modules.
$changed = array();
$modules = $module_handler->getModuleList();
foreach ($modules as $module => $module_info) {
// Collect storage backends exposed by the module.
$storage_types = (array) $module_handler->invoke($module, 'field_storage_info');
if ($storage_types) {
foreach ($fields as $uuid => &$field) {
// Associate storage backends.
if (isset($storage_types[$field['storage']['type']]) && ($field['storage']['module'] !== $module || !$field['storage']['active'])) {
$field['storage']['module'] = $module;
$field['storage']['active'] = TRUE;
$changed[$uuid] = $field;
}
}
}
}
$field_types = Drupal::service('plugin.manager.entity.field.field_type')->getDefinitions();
// Set fields with missing field type or storage modules to inactive.
// Set fields with missing field type modules to inactive.
foreach ($fields as $uuid => &$field) {
// Associate field types.
if (isset($field_types[$field['type']]) && ($field['module'] != $field_types[$field['type']]['provider'] || !$field['active'])) {
@ -428,11 +410,6 @@ function field_sync_field_status() {
$field['active'] = FALSE;
$changed[$uuid] = $field;
}
// Disassociate storage backends.
if (!isset($modules[$field['storage']['module']]) && $field['storage']['active']) {
$field['storage']['active'] = FALSE;
$changed[$uuid] = $field;
}
}
// Store the updated field definitions.
@ -444,8 +421,6 @@ function field_sync_field_status() {
Drupal::config('field.field.' . $field['id'])
->set('module', $field['module'])
->set('active', $field['active'])
->set('storage.module', $field['storage']['module'])
->set('storage.active', $field['storage']['active'])
->save();
}
}
@ -641,7 +616,7 @@ function field_view_value(EntityInterface $entity, $field_name, $item, $display
// Ensure we are working with a BC mode entity.
$entity = $entity->getBCEntity();
if ($field = field_info_field($field_name)) {
if ($field = field_info_field($entity->entityType(), $field_name)) {
// Determine the langcode that will be used by language fallback.
$langcode = field_language($entity, $field_name, $langcode);
@ -731,7 +706,7 @@ function field_view_field(EntityInterface $entity, $field_name, $display_options
$view_mode = '_custom';
// hook_field_attach_display_alter() needs to receive the 'prepared'
// $display_options, so we cannot let preparation happen internally.
$field = field_info_field($field_name);
$field = field_info_field($entity_type, $field_name);
$formatter_manager = drupal_container()->get('plugin.manager.field.formatter');
$display_options = $formatter_manager->prepareConfiguration($field['type'], $display_options);
$formatter = $formatter_manager->getInstance(array(
@ -876,10 +851,12 @@ function template_preprocess_field(&$variables, $hook) {
// Add default CSS classes. Since there can be many fields rendered on a page,
// save some overhead by calling strtr() directly instead of
// drupal_html_class().
$variables['entity_type_css'] = strtr($element['#entity_type'], '_', '-');
$variables['field_name_css'] = strtr($element['#field_name'], '_', '-');
$variables['field_type_css'] = strtr($element['#field_type'], '_', '-');
$variables['attributes']['class'] = array(
'field',
'field-' . $variables['entity_type_css'] . '--' . $variables['field_name_css'],
'field-name-' . $variables['field_name_css'],
'field-type-' . $variables['field_type_css'],
'field-label-' . $element['#label_display'],
@ -894,8 +871,9 @@ function template_preprocess_field(&$variables, $hook) {
$variables['theme_hook_suggestions'] = array(
'field__' . $element['#field_type'],
'field__' . $element['#field_name'],
'field__' . $element['#bundle'],
'field__' . $element['#field_name'] . '__' . $element['#bundle'],
'field__' . $element['#entity_type'] . '__' . $element['#bundle'],
'field__' . $element['#entity_type'] . '__' . $element['#field_name'],
'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'],
);
static $default_attributes;

View File

@ -5,7 +5,6 @@
* Field CRUD API, handling field and field instance creation and deletion.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\Field;
use Drupal\field\FieldException;
@ -111,7 +110,7 @@ function field_purge_batch($batch_size) {
'bundle' => $instance['bundle'],
);
// field_purge_data() will need the field array.
$field = field_info_field_by_id($instance['field_id']);
$field = $instance->getField();
// Retrieve some entities.
$query = $factory->get($entity_type)
->condition('id:' . $field['uuid'] . '.deleted', 1)
@ -121,21 +120,12 @@ function field_purge_batch($batch_size) {
$query->condition($info[$entity_type]['entity_keys']['bundle'], $ids->bundle);
}
$results = $query->execute();
if ($results) {
$entities = array();
foreach ($results as $revision_id => $entity_id) {
// This might not be the revision id if the entity type does not support
// revisions but _field_create_entity_from_ids() checks that and
// disregards this value so there is no harm setting it.
$ids->revision_id = $revision_id;
$ids->entity_id = $entity_id;
$entities[$entity_id] = _field_create_entity_from_ids($ids);
}
field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('instance' => $instance));
foreach ($entities as $entity) {
// Purge the data for the entity.
field_purge_data($entity, $field, $instance);
$entity = _field_create_entity_from_ids($ids);
Drupal::entityManager()->getStorageController($entity_type)->onFieldItemsPurge($entity, $instance);
}
}
else {
@ -155,37 +145,6 @@ function field_purge_batch($batch_size) {
}
}
/**
* Purges the field data for a single field on a single pseudo-entity.
*
* This is basically the same as field_attach_delete() except it only applies to
* a single field. The entity itself is not being deleted, and it is quite
* possible that other field data will remain attached to it.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The pseudo-entity whose field data is being purged.
* @param $field
* The (possibly deleted) field whose data is being purged.
* @param $instance
* The deleted field instance whose data is being purged.
*/
function field_purge_data(EntityInterface $entity, $field, $instance) {
foreach ($entity->{$field->id()} as $value) {
$definition = _field_generate_entity_field_definition($field, $instance);
$items = \Drupal::typedData()->create($definition, $value, $field->id(), $entity);
$items->delete();
}
// Tell the field storage system to purge the data.
module_invoke($field['storage']['module'], 'field_storage_purge', $entity, $field, $instance);
// Let other modules act on purging the data.
foreach (Drupal::moduleHandler()->getImplementations('field_attach_purge') as $module) {
$function = $module . '_field_attach_purge';
$function($entity, $field, $instance);
}
}
/**
* Purges a field instance record from the database.
*
@ -196,10 +155,6 @@ function field_purge_data(EntityInterface $entity, $field, $instance) {
* The instance record to purge.
*/
function field_purge_instance($instance) {
// Notify the storage engine.
$field = field_info_field_by_id($instance['field_id']);
module_invoke($field['storage']['module'], 'field_storage_purge_instance', $instance);
$state = Drupal::state();
$deleted_instances = $state->get('field.instance.deleted');
unset($deleted_instances[$instance['uuid']]);
@ -232,8 +187,8 @@ function field_purge_field($field) {
unset($deleted_fields[$field['uuid']]);
$state->set('field.field.deleted', $deleted_fields);
// Notify the storage engine.
module_invoke($field['storage']['module'], 'field_storage_purge_field', $field);
// Notify the storage layer.
Drupal::entityManager()->getStorageController($field->entity_type)->onFieldPurge($field);
// Clear the cache.
field_info_cache_clear();

View File

@ -8,30 +8,30 @@
*/
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\field\FieldInterface;
/**
* Implements hook_views_data().
*
* Field modules can implement hook_field_views_data() to override
* the default behavior for adding fields.
* Field modules can implement hook_field_views_data() to override the default
* behavior for adding fields.
*/
function field_views_data() {
$data = array();
$module_handler = Drupal::moduleHandler();
foreach (field_info_fields() as $field) {
if ($field['storage']['type'] != 'field_sql_storage') {
continue;
}
if (_field_views_is_sql_entity_type($field)) {
$result = (array) $module_handler->invoke($field['module'], 'field_views_data', array($field));
if (empty($result)) {
$result = field_views_field_default_views_data($field);
}
$module_handler->alter('field_views_data', $result, $field);
$module = $field['module'];
$result = (array) module_invoke($module, 'field_views_data', $field);
if (empty($result)) {
$result = field_views_field_default_views_data($field);
}
Drupal::moduleHandler()->alter('field_views_data', $result, $field, $module);
if (is_array($result)) {
$data = NestedArray::mergeDeep($result, $data);
if (is_array($result)) {
$data = NestedArray::mergeDeep($result, $data);
}
}
}
@ -48,15 +48,34 @@ function field_views_data() {
*/
function field_views_data_alter(&$data) {
foreach (field_info_fields() as $field) {
if ($field['storage']['type'] != 'field_sql_storage') {
continue;
if (_field_views_is_sql_entity_type($field)) {
$function = $field['module'] . '_field_views_data_views_data_alter';
if (function_exists($function)) {
$function($data, $field);
}
}
}
}
$function = $field['module'] . '_field_views_data_views_data_alter';
if (function_exists($function)) {
$function($data, $field);
/**
* Determines whether the entity type the field appears in is SQL based.
*
* @param \Drupal\field\FieldInterface $field
* The field definition.
*
* @return bool
* True if the entity type uses DatabaseStorageController.
*/
function _field_views_is_sql_entity_type(FieldInterface $field) {
$entity_manager = Drupal::entityManager();
try {
if ($entity_manager->getStorageController($field->entity_type) instanceof DatabaseStorageController) {
return TRUE;
}
}
catch (\InvalidArgumentException $e) {
// Disabled entity type, nothing to do.
}
}
/**
@ -64,17 +83,16 @@ function field_views_data_alter(&$data) {
*
* Therefore it looks up in all bundles to find the most used instance.
*/
function field_views_field_label($field_name) {
function field_views_field_label($entity_type, $field_name) {
$label_counter = array();
$all_labels = array();
// Count the amount of instances per label per field.
$instances = field_info_instances();
foreach ($instances as $entity_name => $entity_type) {
foreach ($entity_type as $bundle) {
if (isset($bundle[$field_name])) {
$label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]->label] : 1;
$all_labels[$entity_name][$bundle[$field_name]['label']] = TRUE;
}
$instances = field_info_instances($entity_type);
foreach ($instances as $bundle => $bundle_instances) {
if (isset($bundle_instances[$field_name])) {
$instance = $bundle_instances[$field_name];
$label_counter[$instance->label] = isset($label_counter[$instance->label]) ? ++$label_counter[$instance->label] : 1;
$all_labels[$instance->label] = TRUE;
}
}
if (empty($label_counter)) {
@ -88,111 +106,105 @@ function field_views_field_label($field_name) {
/**
* Default views data implementation for a field.
*
* @param \Drupal\field\FieldInterface $field
* The field definition.
*
* @return array
* The default views data for the field.
*/
function field_views_field_default_views_data($field) {
$field_types = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinitions();
// Check the field module is available.
if (!isset($field_types[$field['type']])) {
return;
}
function field_views_field_default_views_data(FieldInterface $field) {
$data = array();
$current_table = _field_sql_storage_tablename($field);
$revision_table = _field_sql_storage_revision_tablename($field);
// Check the field type is available.
if (!\Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($field['type'])) {
return $data;
}
// Check the field has instances.
if (empty($field['bundles'])) {
return $data;
}
// The list of entity:bundle that this field is used in.
$bundles_names = array();
$supports_revisions = FALSE;
$entity_tables = array();
$current_tables = array();
$revision_tables = array();
$groups = array();
$field_name = $field['field_name'];
$group_name = count($field['bundles']) > 1 ? t('Field') : NULL;
// Grab information about the entity type tables.
$entity_manager = Drupal::entityManager();
$entity_type = $field->entity_type;
$entity_info = $entity_manager->getDefinition($entity_type);
if (!isset($entity_info['base_table'])) {
return $data;
}
$entity_table = $entity_info['base_table'];
$entity_tables = array($entity_table => $entity_type);
$supports_revisions = !empty($entity_info['entity_keys']['revision']) && !empty($entity_info['revision_table']);
if ($supports_revisions) {
$entity_revision_table = $entity_info['revision_table'];
$entity_tables[$entity_revision_table] = $entity_type;
}
// Description of the field tables.
$field_tables = array(
FIELD_LOAD_CURRENT => array(
'table' => DatabaseStorageController::_fieldTableName($field),
'alias' => "{$entity_type}__{$field->name}",
),
);
if ($supports_revisions) {
$field_tables[FIELD_LOAD_REVISION] = array(
'table' => DatabaseStorageController::_fieldRevisionTableName($field),
'alias' => "{$entity_type}_revision__{$field->name}",
);
}
// Build the relationships between the field table and the entity tables.
foreach ($field['bundles'] as $entity => $bundles) {
$entity_info = entity_get_info($entity);
$groups[$entity] = $entity_info['label'];
// Override Node to Content.
if ($groups[$entity] == t('Node')) {
$groups[$entity] = t('Content');
}
// If only one bundle use this as the default name.
if (empty($group_name)) {
$group_name = $groups[$entity];
}
if (!isset($entity_info['base_table'])) {
continue;
}
$entity_tables[$entity_info['base_table']] = $entity;
$current_tables[$entity] = $entity_info['base_table'];
if (isset($entity_info['revision_table'])) {
$entity_tables[$entity_info['revision_table']] = $entity;
$revision_tables[$entity] = $entity_info['revision_table'];
}
$data[$current_table]['table']['join'][$entity_info['base_table']] = array(
'left_field' => $entity_info['entity_keys']['id'],
'field' => 'entity_id',
$table_alias = $field_tables[FIELD_LOAD_CURRENT]['alias'];
$data[$table_alias]['table']['join'][$entity_table] = array(
'left_field' => $entity_info['entity_keys']['id'],
'field' => 'entity_id',
'extra' => array(
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
),
);
if ($supports_revisions) {
$table_alias = $field_tables[FIELD_LOAD_REVISION]['alias'];
$data[$table_alias]['table']['join'][$entity_revision_table] = array(
'left_field' => $entity_info['entity_keys']['revision'],
'field' => 'revision_id',
'extra' => array(
array('field' => 'entity_type', 'value' => $entity),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
),
);
if (!empty($entity_info['entity_keys']['revision']) && !empty($entity_info['revision_table'])) {
$data[$revision_table]['table']['join'][$entity_info['revision_table']] = array(
'left_field' => $entity_info['entity_keys']['revision'],
'field' => 'revision_id',
'extra' => array(
array('field' => 'entity_type', 'value' => $entity),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
),
);
$supports_revisions = TRUE;
}
foreach ($bundles as $bundle) {
$bundles_names[] = t('@entity:@bundle', array('@entity' => $entity, '@bundle' => $bundle));
}
}
$tables = array();
$tables[FIELD_LOAD_CURRENT] = $current_table;
if ($supports_revisions) {
$tables[FIELD_LOAD_REVISION] = $revision_table;
}
// Override Node to Content.
$group_name = ($entity_info['label'] == t('Node')) ? t('Content') : $entity_info['label'];
// Get the list of bundles the field appears in.
$bundles_names = $field->getBundles();
// Build the list of additional fields to add to queries.
$add_fields = array('delta', 'langcode', 'bundle');
foreach ($field['columns'] as $column_name => $attributes) {
$add_fields[] = _field_sql_storage_columnname($field['field_name'], $column_name);
foreach (array_keys($field['columns']) as $column) {
$add_fields[] = DatabaseStorageController::_fieldColumnName($field, $column);
}
// Determine the label to use for the field. We don't have a label available
// at the field level, so we just go through all instances and take the one
// which is used the most frequently.
list($label, $all_labels) = field_views_field_label($entity_type, $field_name);
// Expose data for the field as a whole.
foreach ($field_tables as $type => $table_info) {
$table = $table_info['table'];
$table_alias = $table_info['alias'];
// Note: we don't have a label available here, because we are at the field
// level, not at the instance level. So we just go through all instances
// and take the one which is used the most frequently.
$field_name = $field['field_name'];
list($label, $all_labels) = field_views_field_label($field_name);
foreach ($tables as $type => $table) {
if ($type == FIELD_LOAD_CURRENT) {
$group = $group_name;
$old_column = 'entity_id';
$column = $field['field_name'];
$field_alias = $field_name;
}
else {
$group = t('@group (historical data)', array('@group' => $group_name));
$old_column = 'revision_id';
$column = $field['field_name'] . '-' . $old_column;
$field_alias = $field_name . '-revision_id';
}
$data[$table][$column] = array(
$data[$table_alias][$field_alias] = array(
'group' => $group,
'title' => $label,
'title short' => $label,
@ -203,48 +215,42 @@ function field_views_field_default_views_data($field) {
// entity type + name.
$aliases = array();
$also_known = array();
foreach ($all_labels as $entity_name => $labels) {
if (!isset($current_tables[$entity_name])) {
continue;
foreach ($all_labels as $label_name => $true) {
if ($type == FIELD_LOAD_CURRENT) {
if ($label != $label_name) {
$aliases[] = array(
'base' => $entity_table,
'group' => $group_name,
'title' => $label_name,
'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)),
);
$also_known[] = t('@group: @field', array('@group' => $group_name, '@field' => $label_name));
}
}
foreach ($labels as $label_name => $true) {
if ($type == FIELD_LOAD_CURRENT) {
if ($group_name != $groups[$entity_name] || $label != $label_name) {
$aliases[] = array(
'base' => $current_tables[$entity_name],
'group' => $groups[$entity_name],
'title' => $label_name,
'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)),
);
}
$also_known[] = t('@group: @field', array('@group' => $groups[$entity_name], '@field' => $label_name));
}
else {
if ($group_name != $groups[$entity_name] && $label != $label_name && isset($revision_tables[$entity_name])) {
$aliases[] = array(
'base' => $revision_tables[$entity_name],
'group' => t('@group (historical data)', array('@group' => $groups[$entity_name])),
'title' => $label_name,
'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)),
);
}
$also_known[] = t('@group (historical data): @field', array('@group' => $groups[$entity_name], '@field' => $label_name));
}
elseif ($supports_revisions && $label != $label_name) {
$aliases[] = array(
'base' => $table,
'group' => t('@group (historical data)', array('@group' => $group_name)),
'title' => $label_name,
'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)),
);
$also_known[] = t('@group (historical data): @field', array('@group' => $group_name, '@field' => $label_name));
}
}
if ($aliases) {
$data[$table][$column]['aliases'] = $aliases;
$data[$table][$column]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known)));
$data[$table_alias][$field_alias]['aliases'] = $aliases;
$data[$table_alias][$field_alias]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known)));
}
$keys = array_keys($field['columns']);
$real_field = reset($keys);
$data[$table][$column]['field'] = array(
$data[$table_alias][$field_alias]['field'] = array(
'table' => $table,
'id' => 'field',
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
// Provide a real field for group by.
'real field' => $column . '_' . $real_field,
'real field' => $field_alias . '_' . $real_field,
'additional fields' => $add_fields,
'entity_tables' => $entity_tables,
// Default the element type to div, let the UI change it if necessary.
@ -253,6 +259,7 @@ function field_views_field_default_views_data($field) {
);
}
// Expose data for each field property individually.
foreach ($field['columns'] as $column => $attributes) {
$allow_sort = TRUE;
@ -281,27 +288,32 @@ function field_views_field_default_views_data($field) {
}
if (count($field['columns']) == 1 || $column == 'value') {
$title = t('@label (!name)', array('@label' => $label, '!name' => $field['field_name']));
$title = t('@label (!name)', array('@label' => $label, '!name' => $field_name));
$title_short = $label;
}
else {
$title = t('@label (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $column));
$title = t('@label (!name:!column)', array('@label' => $label, '!name' => $field_name, '!column' => $column));
$title_short = t('@label:!column', array('@label' => $label, '!column' => $column));
}
foreach ($tables as $type => $table) {
// Expose data for the property.
foreach ($field_tables as $type => $table_info) {
$table = $table_info['table'];
$table_alias = $table_info['alias'];
if ($type == FIELD_LOAD_CURRENT) {
$group = $group_name;
}
else {
$group = t('@group (historical data)', array('@group' => $group_name));
}
$column_real_name = $field['storage_details']['sql'][$type][$table][$column];
$column_real_name = DatabaseStorageController::_fieldColumnName($field, $column);
// Load all the fields from the table by default.
$additional_fields = array_values($field['storage_details']['sql'][$type][$table]);
$field_sql_schema = DatabaseStorageController::_fieldSqlSchema($field);
$additional_fields = array_keys($field_sql_schema[$table]['fields']);
$data[$table][$column_real_name] = array(
$data[$table_alias][$column_real_name] = array(
'group' => $group,
'title' => $title,
'title short' => $title_short,
@ -312,130 +324,137 @@ function field_views_field_default_views_data($field) {
// entity type + name.
$aliases = array();
$also_known = array();
foreach ($all_labels as $entity_name => $labels) {
foreach ($labels as $label_name => $true) {
if ($group_name != $groups[$entity_name] || $label != $label_name) {
if (count($field['columns']) == 1 || $column == 'value') {
$alias_title = t('@label (!name)', array('@label' => $label_name, '!name' => $field['field_name']));
}
else {
$alias_title = t('@label (!name:!column)', array('@label' => $label_name, '!name' => $field['field_name'], '!column' => $column));
}
$aliases[] = array(
'group' => $groups[$entity_name],
'title' => $alias_title,
'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $title)),
);
foreach ($all_labels as $label_name => $true) {
if ($label != $label_name) {
if (count($field['columns']) == 1 || $column == 'value') {
$alias_title = t('@label (!name)', array('@label' => $label_name, '!name' => $field_name));
}
$also_known[] = t('@group: @field', array('@group' => $groups[$entity_name], '@field' => $title));
else {
$alias_title = t('@label (!name:!column)', array('@label' => $label_name, '!name' => $field_name, '!column' => $column));
}
$aliases[] = array(
'group' => $group_name,
'title' => $alias_title,
'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $title)),
);
$also_known[] = t('@group: @field', array('@group' => $group_name, '@field' => $title));
}
}
if ($aliases) {
$data[$table][$column_real_name]['aliases'] = $aliases;
$data[$table][$column_real_name]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known)));
$data[$table_alias][$column_real_name]['aliases'] = $aliases;
$data[$table_alias][$column_real_name]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known)));
}
$data[$table][$column_real_name]['argument'] = array(
$data[$table_alias][$column_real_name]['argument'] = array(
'field' => $column_real_name,
'table' => $table,
'id' => $argument,
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
'empty field name' => t('- No value -'),
);
$data[$table][$column_real_name]['filter'] = array(
$data[$table_alias][$column_real_name]['filter'] = array(
'field' => $column_real_name,
'table' => $table,
'id' => $filter,
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
'allow empty' => TRUE,
);
if (!empty($allow_sort)) {
$data[$table][$column_real_name]['sort'] = array(
$data[$table_alias][$column_real_name]['sort'] = array(
'field' => $column_real_name,
'table' => $table,
'id' => $sort,
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
);
}
// Expose additional delta column for multiple value fields.
if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
$title_delta = t('@label (!name:delta)', array('@label' => $label, '!name' => $field['field_name']));
$title_delta = t('@label (!name:delta)', array('@label' => $label, '!name' => $field_name));
$title_short_delta = t('@label:delta', array('@label' => $label));
$data[$table]['delta'] = array(
$data[$table_alias]['delta'] = array(
'group' => $group,
'title' => $title_delta,
'title short' => $title_short_delta,
'help' => t('Delta - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
);
$data[$table]['delta']['field'] = array(
$data[$table_alias]['delta']['field'] = array(
'id' => 'numeric',
);
$data[$table]['delta']['argument'] = array(
$data[$table_alias]['delta']['argument'] = array(
'field' => 'delta',
'table' => $table,
'id' => 'numeric',
'additional fields' => $additional_fields,
'empty field name' => t('- No value -'),
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
);
$data[$table]['delta']['filter'] = array(
$data[$table_alias]['delta']['filter'] = array(
'field' => 'delta',
'table' => $table,
'id' => 'numeric',
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
'allow empty' => TRUE,
);
$data[$table]['delta']['sort'] = array(
$data[$table_alias]['delta']['sort'] = array(
'field' => 'delta',
'table' => $table,
'id' => 'standard',
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
);
}
// Expose additional language column for translatable fields.
if (!empty($field['translatable'])) {
$title_language = t('@label (!name:language)', array('@label' => $label, '!name' => $field['field_name']));
$title_language = t('@label (!name:language)', array('@label' => $label, '!name' => $field_name));
$title_short_language = t('@label:language', array('@label' => $label));
$data[$table]['language'] = array(
$data[$table_alias]['language'] = array(
'group' => $group,
'title' => $title_language,
'title short' => $title_short_language,
'help' => t('Language - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
);
$data[$table]['language']['field'] = array(
$data[$table_alias]['language']['field'] = array(
'id' => 'language',
);
$data[$table]['language']['argument'] = array(
$data[$table_alias]['language']['argument'] = array(
'field' => 'language',
'table' => $table,
'id' => 'language',
'additional fields' => $additional_fields,
'empty field name' => t('- No value -'),
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
);
$data[$table]['language']['filter'] = array(
$data[$table_alias]['language']['filter'] = array(
'field' => 'language',
'table' => $table,
'id' => 'language',
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
'allow empty' => TRUE,
);
$data[$table]['language']['sort'] = array(
$data[$table_alias]['language']['sort'] = array(
'field' => 'language',
'table' => $table,
'id' => 'standard',
'additional fields' => $additional_fields,
'field_name' => $field['field_name'],
'field_name' => $field_name,
'entity_type' => $entity_type,
);
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\field\Entity;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Annotation\EntityType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Config\Entity\ConfigEntityBase;
@ -38,24 +39,35 @@ use Drupal\field\FieldInterface;
class Field extends ConfigEntityBase implements FieldInterface {
/**
* The maximum length of the field ID (machine name), in characters.
* The maximum length of the field name, in characters.
*
* For fields created through Field UI, this includes the 'field_' prefix.
*/
const ID_MAX_LENGTH = 32;
const NAME_MAX_LENGTH = 32;
/**
* The field ID (machine name).
* The field ID.
*
* The ID consists of 2 parts: the entity type and the field name.
*
* Example: node.body, user.field_main_image.
*
* @var string
*/
public $id;
/**
* The field name.
*
* This is the name of the property under which the field values are placed in
* an entity : $entity-{>$field_id}. The maximum length is
* Field:ID_MAX_LENGTH.
* an entity: $entity->{$field_name}. The maximum length is
* Field:NAME_MAX_LENGTH.
*
* Example: body, field_main_image.
*
* @var string
*/
public $id;
public $name;
/**
* The field UUID.
@ -66,6 +78,13 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
public $uuid;
/**
* The name of the entity type the field can be attached to.
*
* @var string
*/
public $entity_type;
/**
* The field type.
*
@ -118,16 +137,6 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
public $translatable = FALSE;
/**
* The entity types on which the field is allowed to have instances.
*
* If empty or not specified, the field is allowed to have instances in any
* entity type.
*
* @var array
*/
public $entity_types = array();
/**
* Flag indicating whether the field is available for editing.
*
@ -142,25 +151,6 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
public $locked = FALSE;
/**
* The field storage definition.
*
* An array of key/value pairs identifying the storage backend to use for the
* field:
* - type: (string) The storage backend used by the field. Storage backends
* are defined by modules that implement hook_field_storage_info().
* - settings: (array) A sub-array of key/value pairs of settings. The keys
* and default values are defined by the storage backend in the 'settings'
* entry of hook_field_storage_info().
* - module: (string, read-only) The name of the module that implements the
* storage backend.
* - active: (integer, read-only) TRUE if the module that implements the
* storage backend is currently enabled, FALSE otherwise.
*
* @var array
*/
public $storage = array();
/**
* The custom storage indexes for the field data storage.
*
@ -201,13 +191,6 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
protected $schema;
/**
* The storage information for the field.
*
* @var array
*/
protected $storageDetails;
/**
* The original field.
*
@ -223,8 +206,9 @@ class Field extends ConfigEntityBase implements FieldInterface {
* elements will be used to set the corresponding properties on the class;
* see the class property documentation for details. Some array elements
* have special meanings and a few are required. Special elements are:
* - id: required. As a temporary Backwards Compatibility layer right now,
* - name: required. As a temporary Backwards Compatibility layer right now,
* a 'field_name' property can be accepted in place of 'id'.
* - entity_type: required.
* - type: required.
*
* In most cases, Field entities are created via
@ -237,26 +221,29 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
public function __construct(array $values, $entity_type = 'field_entity') {
// Check required properties.
if (empty($values['name'])) {
throw new FieldException('Attempt to create an unnamed field.');
}
if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['name'])) {
throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character');
}
if (empty($values['type'])) {
throw new FieldException('Attempt to create a field with no type.');
}
// Temporary BC layer: accept both 'id' and 'field_name'.
// @todo $field_name and the handling for it will be removed in
// http://drupal.org/node/1953408.
if (empty($values['field_name']) && empty($values['id'])) {
throw new FieldException('Attempt to create an unnamed field.');
}
if (empty($values['id'])) {
$values['id'] = $values['field_name'];
unset($values['field_name']);
}
if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['id'])) {
throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character');
if (empty($values['entity_type'])) {
throw new FieldException('Attempt to create a field with no entity_type.');
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->entity_type . '.' . $this->name;
}
/**
* {@inheritdoc}
*/
@ -266,12 +253,12 @@ class Field extends ConfigEntityBase implements FieldInterface {
'uuid',
'status',
'langcode',
'name',
'entity_type',
'type',
'settings',
'module',
'active',
'entity_types',
'storage',
'locked',
'cardinality',
'translatable',
@ -297,7 +284,7 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
public function save() {
// Clear the derived data about the field.
unset($this->schema, $this->storageDetails);
unset($this->schema);
if ($this->isNew()) {
return $this->saveNew();
@ -319,18 +306,20 @@ class Field extends ConfigEntityBase implements FieldInterface {
* In case of failures at the configuration storage level.
*/
protected function saveNew() {
$module_handler = \Drupal::moduleHandler();
$entity_manager = \Drupal::entityManager();
$storage_controller = $entity_manager->getStorageController($this->entityType);
// Field name cannot be longer than Field::ID_MAX_LENGTH characters. We
// use drupal_strlen() because the DB layer assumes that column widths
// Assign the ID.
$this->id = $this->id();
// Field name cannot be longer than Field::NAME_MAX_LENGTH characters. We
// use Unicode::strlen() because the DB layer assumes that column widths
// are given in characters rather than bytes.
if (drupal_strlen($this->id) > static::ID_MAX_LENGTH) {
if (Unicode::strlen($this->name) > static::NAME_MAX_LENGTH) {
throw new FieldException(format_string(
'Attempt to create a field with an ID longer than @max characters: %id', array(
'@max' => static::ID_MAX_LENGTH,
'%id' => $this->id,
'Attempt to create a field with an ID longer than @max characters: %name', array(
'@max' => static::NAME_MAX_LENGTH,
'%name' => $this->name,
)
));
}
@ -338,17 +327,17 @@ class Field extends ConfigEntityBase implements FieldInterface {
// Ensure the field name is unique (we do not care about deleted fields).
if ($prior_field = $storage_controller->load($this->id)) {
$message = $prior_field->active ?
'Attempt to create field name %id which already exists and is active.' :
'Attempt to create field name %id which already exists, although it is inactive.';
throw new FieldException(format_string($message, array('%id' => $this->id)));
'Attempt to create field name %name which already exists and is active.' :
'Attempt to create field name %name which already exists, although it is inactive.';
throw new FieldException(format_string($message, array('%name' => $this->name)));
}
// Disallow reserved field names. This can't prevent all field name
// collisions with existing entity properties, but some is better than
// none.
foreach ($entity_manager->getDefinitions() as $type => $info) {
if (in_array($this->id, $info['entity_keys'])) {
throw new FieldException(format_string('Attempt to create field %id which is reserved by entity type %type.', array('%id' => $this->id, '%type' => $type)));
if (in_array($this->name, $info['entity_keys'])) {
throw new FieldException(format_string('Attempt to create field %name which is reserved by entity type %type.', array('%name' => $this->name, '%type' => $type)));
}
}
@ -364,23 +353,8 @@ class Field extends ConfigEntityBase implements FieldInterface {
// definition is passed to the various hooks and written to config.
$this->settings += $field_type['settings'];
// Provide default storage.
$this->storage += array(
'type' => variable_get('field_storage_default', 'field_sql_storage'),
'settings' => array(),
);
// Check that the storage type is known.
$storage_type = field_info_storage_types($this->storage['type']);
if (!$storage_type) {
throw new FieldException(format_string('Attempt to create a field with unknown storage type %type.', array('%type' => $this->storage['type'])));
}
$this->storage['module'] = $storage_type['module'];
$this->storage['active'] = TRUE;
// Provide default storage settings.
$this->storage['settings'] += $storage_type['settings'];
// Invoke the storage backend's hook_field_storage_create_field().
$module_handler->invoke($this->storage['module'], 'field_storage_create_field', array($this));
// Notify the entity storage controller.
$entity_manager->getStorageController($this->entity_type)->onFieldCreate($this);
// Save the configuration.
$result = parent::save();
@ -402,7 +376,8 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
protected function saveUpdated() {
$module_handler = \Drupal::moduleHandler();
$storage_controller = \Drupal::entityManager()->getStorageController($this->entityType);
$entity_manager = \Drupal::entityManager();
$storage_controller = $entity_manager->getStorageController($this->entityType);
$original = $storage_controller->loadUnchanged($this->id());
$this->original = $original;
@ -411,11 +386,8 @@ class Field extends ConfigEntityBase implements FieldInterface {
if ($this->type != $original->type) {
throw new FieldException("Cannot change an existing field's type.");
}
if ($this->entity_types != $original->entity_types) {
throw new FieldException("Cannot change an existing field's entity_types property.");
}
if ($this->storage['type'] != $original->storage['type']) {
throw new FieldException("Cannot change an existing field's storage type.");
if ($this->entity_type != $original->entity_type) {
throw new FieldException("Cannot change an existing field's entity_type.");
}
// Make sure all settings are present, so that a complete field definition
@ -427,11 +399,10 @@ class Field extends ConfigEntityBase implements FieldInterface {
// invokes hook_field_update_forbid().
$module_handler->invokeAll('field_update_forbid', array($this, $original));
// Tell the storage engine to update the field by invoking the
// hook_field_storage_update_field(). The storage engine can reject the
// definition update as invalid by raising an exception, which stops
// execution before the definition is written to config.
$module_handler->invoke($this->storage['module'], 'field_storage_update_field', array($this, $original));
// Notify the storage controller. The controller can reject the definition
// update as invalid by raising an exception, which stops execution before
// the definition is written to config.
$entity_manager->getStorageController($this->entity_type)->onFieldUpdate($this, $original);
// Save the configuration.
$result = parent::save();
@ -445,16 +416,13 @@ class Field extends ConfigEntityBase implements FieldInterface {
*/
public function delete() {
if (!$this->deleted) {
$module_handler = \Drupal::moduleHandler();
$instance_controller = \Drupal::entityManager()->getStorageController('field_instance');
$state = \Drupal::state();
// Delete all non-deleted instances.
$instance_ids = array();
foreach ($this->getBundles() as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$instance_ids[] = "$entity_type.$bundle.$this->id";
}
foreach ($this->getBundles() as $bundle) {
$instance_ids[] = "{$this->entity_type}.$bundle.{$this->name}";
}
foreach ($instance_controller->loadMultiple($instance_ids) as $instance) {
// By default, FieldInstance::delete() will automatically try to delete
@ -464,9 +432,7 @@ class Field extends ConfigEntityBase implements FieldInterface {
$instance->delete(FALSE);
}
// Mark field data for deletion by invoking
// hook_field_storage_delete_field().
$module_handler->invoke($this->storage['module'], 'field_storage_delete_field', array($this));
\Drupal::entityManager()->getStorageController($this->entity_type)->onFieldDelete($this);
// Delete the configuration of this field and save the field configuration
// in the key_value table so we can use it later during
@ -526,33 +492,14 @@ class Field extends ConfigEntityBase implements FieldInterface {
return $schema['columns'];
}
/**
* {@inheritdoc}
*/
public function getStorageDetails() {
if (!isset($this->storageDetails)) {
$module_handler = \Drupal::moduleHandler();
// Collect the storage details from the storage backend, and let other
// modules alter it. This invokes hook_field_storage_details() and
// hook_field_storage_details_alter().
$details = (array) $module_handler->invoke($this->storage['module'], 'field_storage_details', array($this));
$module_handler->alter('field_storage_details', $details, $this);
$this->storageDetails = $details;
}
return $this->storageDetails;
}
/**
* {@inheritdoc}
*/
public function getBundles() {
if (empty($this->deleted)) {
$map = field_info_field_map();
if (isset($map[$this->id]['bundles'])) {
return $map[$this->id]['bundles'];
if (isset($map[$this->entity_type][$this->name]['bundles'])) {
return $map[$this->entity_type][$this->name]['bundles'];
}
}
return array();
@ -562,7 +509,7 @@ class Field extends ConfigEntityBase implements FieldInterface {
* {@inheritdoc}
*/
public function getFieldName() {
return $this->id;
return $this->name;
}
/**
@ -670,7 +617,7 @@ class Field extends ConfigEntityBase implements FieldInterface {
return $this->uuid;
case 'field_name':
return $this->id;
return $this->name;
case 'columns':
$this->getSchema();
@ -683,10 +630,6 @@ class Field extends ConfigEntityBase implements FieldInterface {
case 'bundles':
$bundles = $this->getBundles();
return $bundles;
case 'storage_details':
$this->getStorageDetails();
return $this->storageDetails;
}
return $this->{$offset};
@ -726,19 +669,19 @@ class Field extends ConfigEntityBase implements FieldInterface {
* TRUE if the field has data for any entity; FALSE otherwise.
*/
public function hasData() {
$storage_details = $this->getSchema();
$columns = array_keys($storage_details['columns']);
$factory = \Drupal::service('entity.query');
foreach ($this->getBundles() as $entity_type => $bundle) {
if ($this->getBundles()) {
$storage_details = $this->getSchema();
$columns = array_keys($storage_details['columns']);
$factory = \Drupal::service('entity.query');
// Entity Query throws an exception if there is no base table.
$entity_info = \Drupal::entityManager()->getDefinition($entity_type);
$entity_info = \Drupal::entityManager()->getDefinition($this->entity_type);
if (!isset($entity_info['base_table'])) {
continue;
return FALSE;
}
$query = $factory->get($entity_type);
$query = $factory->get($this->entity_type);
$group = $query->orConditionGroup();
foreach ($columns as $column) {
$group->exists($this->id() . '.' . $column);
$group->exists($this->name . '.' . $column);
}
$result = $query
->condition($group)

View File

@ -7,6 +7,7 @@
namespace Drupal\field\Entity;
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\Annotation\EntityType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Config\Entity\ConfigEntityBase;
@ -35,7 +36,7 @@ use Drupal\field\FieldInstanceInterface;
class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
/**
* The instance ID (machine name).
* The instance ID.
*
* The ID consists of 3 parts: the entity type, bundle and the field name.
*
@ -235,10 +236,10 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
public function __construct(array $values, $entity_type = 'field_instance') {
// Accept incoming 'field_name' instead of 'field_uuid', for easier DX on
// creation of new instances.
if (isset($values['field_name']) && !isset($values['field_uuid'])) {
$field = field_info_field($values['field_name']);
if (isset($values['field_name']) && isset($values['entity_type']) && !isset($values['field_uuid'])) {
$field = field_info_field($values['entity_type'], $values['field_name']);
if (!$field) {
throw new FieldException(format_string('Attempt to create an instance of unknown, disabled, or deleted field @field_id', array('@field_id' => $values['field_name'])));
throw new FieldException(format_string('Attempt to create an instance of field @field_name that does not exist on entity type @entity_type.', array('@field_name' => $values['field_name'], '@entity_type' => $values['entity_type'])));
}
$values['field_uuid'] = $field->uuid;
}
@ -267,16 +268,16 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
// Check required properties.
if (empty($values['entity_type'])) {
throw new FieldException(format_string('Attempt to create an instance of field @field_id without an entity type.', array('@field_id' => $this->field->id)));
throw new FieldException(format_string('Attempt to create an instance of field @field_name without an entity_type.', array('@field_name' => $this->field->name)));
}
if (empty($values['bundle'])) {
throw new FieldException(format_string('Attempt to create an instance of field @field_id without a bundle.', array('@field_id' => $this->field->id)));
throw new FieldException(format_string('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $this->field->name)));
}
// 'Label' defaults to the field ID (mostly useful for field instances
// 'Label' defaults to the field name (mostly useful for field instances
// created in tests).
$values += array(
'label' => $this->field->id,
'label' => $this->field->name,
);
parent::__construct($values, $entity_type);
}
@ -285,7 +286,7 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
* {@inheritdoc}
*/
public function id() {
return $this->entity_type . '.' . $this->bundle . '.' . $this->field->id;
return $this->entity_type . '.' . $this->bundle . '.' . $this->field->name;
}
/**
@ -357,14 +358,9 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
protected function saveNew() {
$instance_controller = \Drupal::entityManager()->getStorageController($this->entityType);
// Check that the field can be attached to this entity type.
if (!empty($this->field->entity_types) && !in_array($this->entity_type, $this->field->entity_types)) {
throw new FieldException(format_string('Attempt to create an instance of field @field_id on forbidden entity type @entity_type.', array('@field_id' => $this->field->id, '@entity_type' => $this->entity_type)));
}
// Ensure the field instance is unique within the bundle.
if ($prior_instance = $instance_controller->load($this->id())) {
throw new FieldException(format_string('Attempt to create an instance of field @field_id on bundle @bundle that already has an instance of that field.', array('@field_id' => $this->field->id, '@bundle' => $this->bundle)));
throw new FieldException(format_string('Attempt to create an instance of field %name on bundle @bundle that already has an instance of that field.', array('%name' => $this->field->name, '@bundle' => $this->bundle)));
}
// Set the field UUID.
@ -412,6 +408,9 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
// Ensure default values are present.
$this->prepareSave();
// Notify the entity storage controller.
\Drupal::entityManager()->getStorageController($this->entity_type)->onInstanceUpdate($this);
// Save the configuration.
$result = parent::save();
field_cache_clear();
@ -439,7 +438,6 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
*/
public function delete($field_cleanup = TRUE) {
if (!$this->deleted) {
$module_handler = \Drupal::moduleHandler();
$state = \Drupal::state();
// Delete the configuration of this instance and save the configuration
@ -453,16 +451,15 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
parent::delete();
// Notify the entity storage controller.
\Drupal::entityManager()->getStorageController($this->entity_type)->onInstanceDelete($this);
// Clear the cache.
field_cache_clear();
// Mark instance data for deletion by invoking
// hook_field_storage_delete_instance().
$module_handler->invoke($this->field->storage['module'], 'field_storage_delete_instance', array($this));
// Remove the instance from the entity form displays.
if ($form_display = entity_load('entity_form_display', $this->entity_type . '.' . $this->bundle . '.default')) {
$form_display->removeComponent($this->field->id())->save();
$form_display->removeComponent($this->field->name)->save();
}
// Remove the instance from the entity displays.
@ -472,7 +469,7 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
$ids[] = $this->entity_type . '.' . $this->bundle . '.' . $view_mode;
}
foreach (entity_load_multiple('entity_display', $ids) as $display) {
$display->removeComponent($this->field->id())->save();
$display->removeComponent($this->field->name)->save();
}
// Delete the field itself if we just deleted its last instance.
@ -493,7 +490,7 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
* {@inheritdoc}
*/
public function getFieldName() {
return $this->field->id;
return $this->field->name;
}
/**
@ -620,7 +617,7 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
return $this->field_uuid;
}
if ($offset == 'field_name') {
return $this->field->id;
return $this->field->name;
}
return $this->{$offset};
}

View File

@ -165,10 +165,10 @@ class FieldInfo {
* Collects a lightweight map of fields across bundles.
*
* @return
* An array keyed by field name. Each value is an array with two entries:
* An array keyed by entity type. Each value is an array which keys are
* field names and value is an array with two entries:
* - type: The field type.
* - bundles: The bundles in which the field appears, as an array with
* entity types as keys and the array of bundle names as values.
* - bundles: The bundles in which the field appears.
*/
public function getFieldMap() {
// Read from the "static" cache.
@ -191,7 +191,7 @@ class FieldInfo {
// Get active fields.
foreach (config_get_storage_names_with_prefix('field.field.') as $config_id) {
$field_config = $this->config->get($config_id)->get();
if ($field_config['active'] && $field_config['storage']['active']) {
if ($field_config['active']) {
$fields[$field_config['uuid']] = $field_config;
}
}
@ -203,8 +203,8 @@ class FieldInfo {
// entity types.
if (isset($fields[$field_uuid])) {
$field = $fields[$field_uuid];
$map[$field['id']]['bundles'][$instance_config['entity_type']][] = $instance_config['bundle'];
$map[$field['id']]['type'] = $field['type'];
$map[$instance_config['entity_type']][$field['name']]['bundles'][] = $instance_config['bundle'];
$map[$instance_config['entity_type']][$field['name']]['type'] = $field['type'];
}
}
@ -244,7 +244,7 @@ class FieldInfo {
// Fill the name/ID map.
foreach ($this->fieldsById as $field) {
if (!$field['deleted']) {
$this->fieldIdsByName[$field['id']] = $field['uuid'];
$this->fieldIdsByName[$field->entity_type][$field->name] = $field['uuid'];
}
}
@ -280,7 +280,7 @@ class FieldInfo {
$this->getFields();
foreach (field_read_instances() as $instance) {
$field = $this->getField($instance['field_name']);
$field = $this->getField($instance['entity_type'], $instance['field_name']);
$instance = $this->prepareInstance($instance, $field['type']);
$this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
}
@ -305,36 +305,38 @@ class FieldInfo {
*
* This method only retrieves active, non-deleted fields.
*
* @param $field_name
* @param string $entity_type
* The entity type.
* @param string $field_name
* The field name.
*
* @return
* The field definition, or NULL if no field was found.
*/
public function getField($field_name) {
public function getField($entity_type, $field_name) {
// Read from the "static" cache.
if (isset($this->fieldIdsByName[$field_name])) {
$field_id = $this->fieldIdsByName[$field_name];
if (isset($this->fieldIdsByName[$entity_type][$field_name])) {
$field_id = $this->fieldIdsByName[$entity_type][$field_name];
return $this->fieldsById[$field_id];
}
if (isset($this->unknownFields[$field_name])) {
if (isset($this->unknownFields[$entity_type][$field_name])) {
return;
}
// Do not check the (large) persistent cache, but read the definition.
// Cache miss: read from definition.
if ($field = field_read_field($field_name)) {
if ($field = entity_load('field_entity', $entity_type . '.' . $field_name)) {
$field = $this->prepareField($field);
// Save in the "static" cache.
$this->fieldsById[$field['uuid']] = $field;
$this->fieldIdsByName[$field['field_name']] = $field['uuid'];
$this->fieldIdsByName[$entity_type][$field_name] = $field['uuid'];
return $field;
}
else {
$this->unknownFields[$field_name] = TRUE;
$this->unknownFields[$entity_type][$field_name] = TRUE;
}
}
@ -369,7 +371,7 @@ class FieldInfo {
// Store in the static cache.
$this->fieldsById[$field['uuid']] = $field;
if (!$field['deleted']) {
$this->fieldIdsByName[$field['field_name']] = $field['uuid'];
$this->fieldIdsByName[$field->entity_type][$field->name] = $field['uuid'];
}
return $field;
@ -413,7 +415,7 @@ class FieldInfo {
if (!isset($this->fieldsById[$field['uuid']])) {
$this->fieldsById[$field['uuid']] = $field;
if (!$field['deleted']) {
$this->fieldIdsByName[$field['field_name']] = $field['uuid'];
$this->fieldIdsByName[$field->entity_type][$field->name] = $field['uuid'];
}
}
}
@ -435,43 +437,46 @@ class FieldInfo {
}
// Cache miss: collect from the definitions.
$field_map = $this->getFieldMap();
$instances = array();
$fields = array();
// Do not return anything for unknown entity types.
if (entity_get_info($entity_type)) {
if (entity_get_info($entity_type) && !empty($field_map[$entity_type])) {
// Collect names of fields and instances involved in the bundle, using the
// field map. The field map is already filtered to active, non-deleted
// fields and instances, so those are kept out of the persistent caches.
$config_ids = array();
foreach ($this->getFieldMap() as $field_name => $field_data) {
if (isset($field_data['bundles'][$entity_type]) && in_array($bundle, $field_data['bundles'][$entity_type])) {
$config_ids[$field_name] = "$entity_type.$bundle.$field_name";
foreach ($field_map[$entity_type] as $field_name => $field_data) {
if (in_array($bundle, $field_data['bundles'])) {
$config_ids["$entity_type.$field_name"] = "$entity_type.$bundle.$field_name";
}
}
// Load and prepare the corresponding fields and instances entities.
if ($config_ids) {
// Place the fields in our global "static".
$loaded_fields = entity_load_multiple('field_entity', array_keys($config_ids));
$loaded_instances = entity_load_multiple('field_instance', array_values($config_ids));
foreach ($loaded_instances as $instance) {
$field = $loaded_fields[$instance['field_name']];
$instance = $this->prepareInstance($instance, $field['type']);
$instances[$field['field_name']] = $instance;
// If the field is not in our global "static" list yet, add it.
foreach ($loaded_fields as $field) {
if (!isset($this->fieldsById[$field['uuid']])) {
$field = $this->prepareField($field);
$this->fieldsById[$field['uuid']] = $field;
$this->fieldIdsByName[$field['field_name']] = $field['uuid'];
$this->fieldIdsByName[$field->entity_type][$field->name] = $field['uuid'];
}
$fields[] = $this->fieldsById[$field['uuid']];
}
// Then collect the instances.
$loaded_instances = entity_load_multiple('field_instance', array_values($config_ids));
foreach ($loaded_instances as $instance) {
$field = $instance->getField();
$instance = $this->prepareInstance($instance, $field['type']);
$instances[$field['field_name']] = $instance;
}
}
}
@ -567,7 +572,6 @@ class FieldInfo {
public function prepareField($field) {
// Make sure all expected field settings are present.
$field['settings'] += $this->fieldTypeManager->getDefaultSettings($field['type']);
$field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
return $field;
}

View File

@ -136,7 +136,6 @@ class FieldInstanceStorageController extends ConfigStorageController {
// Translate "do not include inactive fields" into actual conditions.
if (!$include_inactive) {
$conditions['field.active'] = TRUE;
$conditions['field.storage.active'] = TRUE;
}
// Collect matching instances.
@ -155,17 +154,13 @@ class FieldInstanceStorageController extends ConfigStorageController {
// Extract the actual value against which the condition is checked.
switch ($key) {
case 'field_name':
$checked_value = $field->id;
$checked_value = $field->name;
break;
case 'field.active':
$checked_value = $field->active;
break;
case 'field.storage.active':
$checked_value = $field->storage['active'];
break;
case 'field_id':
$checked_value = $instance->field_uuid;
break;

View File

@ -44,34 +44,6 @@ interface FieldInterface extends ConfigEntityInterface, FieldDefinitionInterface
*/
public function getColumns();
/**
* Returns information about how the storage backend stores the field data.
*
* The content of the returned value depends on the storage backend, and some
* storage backends might provide no information.
*
* It is strongly discouraged to use this information to perform direct write
* operations to the field data storage, bypassing the regular field saving
* APIs.
*
* Example return value for the default field_sql_storage backend:
* - 'sql'
* - FIELD_LOAD_CURRENT
* - Table name (string).
* - Table schema (array)
* - FIELD_LOAD_REVISION
* - Table name (string).
* - Table schema (array).
*
* @return array
* The storage details.
* - The first dimension is a store type (sql, solr, etc).
* - The second dimension indicates the age of the values in the store
* FIELD_LOAD_CURRENT or FIELD_LOAD_REVISION.
* - Other dimensions are specific to the field storage backend.
*/
public function getStorageDetails();
/**
* Returns the list of bundles where the field has instances.
*

View File

@ -98,9 +98,10 @@ class FieldStorageController extends ConfigStorageController {
unset($conditions['include_deleted']);
// Get fields stored in configuration.
if (isset($conditions['field_name'])) {
if (isset($conditions['entity_type']) && isset($conditions['field_name'])) {
// Optimize for the most frequent case where we do have a specific ID.
$fields = $this->entityManager->getStorageController($this->entityType)->loadMultiple(array($conditions['field_name']));
$id = $conditions['entity_type'] . $conditions['field_name'];
$fields = $this->entityManager->getStorageController($this->entityType)->loadMultiple(array($id));
}
else {
// No specific ID, we need to examine all existing fields.
@ -118,7 +119,6 @@ class FieldStorageController extends ConfigStorageController {
// Translate "do not include inactive instances" into actual conditions.
if (!$include_inactive) {
$conditions['active'] = TRUE;
$conditions['storage.active'] = TRUE;
}
// Collect matching fields.
@ -127,12 +127,8 @@ class FieldStorageController extends ConfigStorageController {
foreach ($conditions as $key => $value) {
// Extract the actual value against which the condition is checked.
switch ($key) {
case 'storage.active':
$checked_value = $field->storage['active'];
break;
case 'field_name';
$checked_value = $field->id;
$checked_value = $field->name;
break;
default:

View File

@ -42,8 +42,8 @@ class LegacyConfigField extends ConfigField {
$entity = $this->getParent();
$langcode = $entity->language()->id;
if (isset($legacy_errors[$this->getInstance()->getField()->id()][$langcode])) {
foreach ($legacy_errors[$this->getInstance()->getField()->id()][$langcode] as $delta => $item_errors) {
if (isset($legacy_errors[$this->getInstance()->getField()->name][$langcode])) {
foreach ($legacy_errors[$this->getInstance()->getField()->name][$langcode] as $delta => $item_errors) {
foreach ($item_errors as $item_error) {
// We do not have the information about which column triggered the
// error, so assume the first column...

View File

@ -90,8 +90,8 @@ abstract class LegacyConfigFieldItem extends ConfigFieldItemBase implements Prep
$langcode = $entity->language()->id;
$entity_id = $entity->id();
// hook_field_attach_load() receives items keyed by entity id, and alter
// then by reference.
// hook_field_load() receives items keyed by entity id, and alters then by
// reference.
$items = array($entity_id => array(0 => $this->getValue(TRUE)));
$args = array(
$entity->entityType(),

View File

@ -35,7 +35,7 @@ class FieldList extends Numeric {
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$field = field_info_field($this->definition['field_name']);
$field = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
$this->allowed_values = options_allowed_values($field);
}

View File

@ -35,7 +35,7 @@ class ListString extends String {
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$field = field_info_field($this->definition['field_name']);
$field = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
$this->allowed_values = options_allowed_values($field);
}

View File

@ -7,8 +7,9 @@
namespace Drupal\field\Plugin\views\field;
use Drupal\Core\Language\Language;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\Language;
use Drupal\field\Plugin\Type\Formatter\FormatterPluginManager;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
@ -111,7 +112,7 @@ class Field extends FieldPluginBase {
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$this->field_info = $field = field_info_field($this->definition['field_name']);
$this->field_info = $field = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
$this->multiple = FALSE;
$this->limit_values = FALSE;
@ -286,7 +287,8 @@ class Field extends FieldPluginBase {
}
$this->ensureMyTable();
$column = _field_sql_storage_columnname($this->definition['field_name'], $this->options['click_sort_column']);
$field = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
$column = DatabaseStorageController::_fieldColumnName($field, $this->options['click_sort_column']);
if (!isset($this->aliases[$column])) {
// Column is not in query; add a sort on it (without adding the column).
$this->aliases[$column] = $this->tableAlias . '.' . $column;
@ -298,7 +300,7 @@ class Field extends FieldPluginBase {
$options = parent::defineOptions();
// defineOptions runs before init/construct, so no $this->field_info
$field = field_info_field($this->definition['field_name']);
$field = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
$field_type = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($field['type']);
$column_names = array_keys($field['columns']);
$default_column = '';

View File

@ -20,7 +20,7 @@ use Drupal\Component\Annotation\PluginID;
class FieldList extends ManyToOne {
public function getValueOptions() {
$field = field_info_field($this->definition['field_name']);
$field = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
$this->value_options = list_allowed_values($field);
}

View File

@ -28,7 +28,7 @@ class EntityReverse extends RelationshipPluginBase {
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$this->field_info = field_info_field($this->definition['field_name']);
$this->field_info = field_info_field($this->definition['entity_type'], $this->definition['field_name']);
}
/**

View File

@ -14,7 +14,7 @@ class ActiveTest extends FieldTestBase {
*
* @var array
*/
public static $modules = array('field_test');
public static $modules = array('field_test', 'entity_test');
public static function getInfo() {
return array(
@ -28,62 +28,28 @@ class ActiveTest extends FieldTestBase {
* Test that fields are properly marked active or inactive.
*/
function testActive() {
$field_definition = array(
'field_name' => 'field_1',
$field_name = 'field_1';
entity_create('field_entity', array(
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'test_field',
// For this test, we need a storage backend provided by a different
// module than field_test.module.
'storage' => array(
'type' => 'field_sql_storage',
),
);
entity_create('field_entity', $field_definition)->save();
))->save();
// Test disabling and enabling:
// - the field type module,
// - the storage module,
// - both.
$this->_testActiveHelper($field_definition, array('field_test'));
$this->_testActiveHelper($field_definition, array('field_sql_storage'));
$this->_testActiveHelper($field_definition, array('field_test', 'field_sql_storage'));
}
// Check that the field is correctly found.
$field = field_read_field('entity_test', $field_name);
$this->assertFalse(empty($field), 'The field was properly read.');
/**
* Helper function for testActive().
*
* Test dependency between a field and a set of modules.
*
* @param $field_definition
* A field definition.
* @param $modules
* An aray of module names. The field will be tested to be inactive as long
* as any of those modules is disabled.
*/
function _testActiveHelper($field_definition, $modules) {
$field_name = $field_definition['field_name'];
// Disable the module providing the field type, and check that the field is
// found only if explicitly requesting inactive fields.
module_disable(array('field_test'));
$field = field_read_field('entity_test', $field_name);
$this->assertTrue(empty($field), 'The field is marked inactive when the field type is absent.');
$field = field_read_field('entity_test', $field_name, array('include_inactive' => TRUE));
$this->assertFalse(empty($field), 'The field is properly read when explicitly fetching inactive fields.');
// Read the field.
$field = field_read_field($field_name);
$this->assertTrue($field_definition <= $field, 'The field was properly read.');
module_disable($modules, FALSE);
$fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
$this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, 'The field is properly read when explicitly fetching inactive fields.');
// Re-enable modules one by one, and check that the field is still inactive
// while some modules remain disabled.
while ($modules) {
$field = field_read_field($field_name);
$this->assertTrue(empty($field), format_string('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules))));
$module = array_shift($modules);
module_enable(array($module), FALSE);
}
// Check that the field is active again after all modules have been
// enabled.
$field = field_read_field($field_name);
$this->assertTrue($field_definition <= $field, 'The field was was marked active.');
// Re-enable the module, and check that the field is active again.
module_enable(array('field_test'));
$field = field_read_field('entity_test', $field_name);
$this->assertFalse(empty($field), 'The field was was marked active.');
}
}

View File

@ -7,10 +7,10 @@
namespace Drupal\field\Tests;
use Drupal\field\Entity\FieldInstance;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\FieldInterface;
use Drupal\Core\Language\Language;
/**
* Unit test class for field bulk delete and batch purge functionality.
@ -50,7 +50,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
*
* @var array
*/
protected $entity_type = 'test_entity';
protected $entity_type = 'entity_test';
public static function getInfo() {
return array(
@ -114,14 +114,16 @@ class BulkDeleteTest extends FieldUnitTestBase {
// Create two fields.
$field = entity_create('field_entity', array(
'field_name' => 'bf_1',
'name' => 'bf_1',
'entity_type' => $this->entity_type,
'type' => 'test_field',
'cardinality' => 1
));
$field->save();
$this->fields[] = $field;
$field = entity_create('field_entity', array(
'field_name' => 'bf_2',
'name' => 'bf_2',
'entity_type' => $this->entity_type,
'type' => 'test_field',
'cardinality' => 4
));
@ -130,11 +132,10 @@ class BulkDeleteTest extends FieldUnitTestBase {
// For each bundle, create an instance of each field, and 10
// entities with values for each field.
$this->entity_type = 'entity_test';
foreach ($this->bundles as $bundle) {
foreach ($this->fields as $field) {
entity_create('field_instance', array(
'field_name' => $field->id(),
'field_name' => $field->name,
'entity_type' => $this->entity_type,
'bundle' => $bundle,
))->save();
@ -165,7 +166,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
function testDeleteFieldInstance() {
$bundle = reset($this->bundles);
$field = reset($this->fields);
$field_name = $field->id();
$field_name = $field->name;
$factory = \Drupal::service('entity.query');
// There are 10 entities of this bundle.
@ -175,7 +176,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
$this->assertEqual(count($found), 10, 'Correct number of entities found before deleting');
// Delete the instance.
$instance = field_info_instance($this->entity_type, $field->id(), $bundle);
$instance = field_info_instance($this->entity_type, $field->name, $bundle);
$instance->delete();
// The instance still exists, deleted.
@ -184,6 +185,17 @@ class BulkDeleteTest extends FieldUnitTestBase {
$instance = $instances[0];
$this->assertEqual($instance['bundle'], $bundle, 'The deleted instance is for the correct bundle');
// Check that the actual stored content did not change during delete.
$schema = DatabaseStorageController::_fieldSqlSchema($field);
$table = DatabaseStorageController::_fieldTableName($field);
$column = DatabaseStorageController::_fieldColumnName($field, 'value');
$result = db_select($table, 't')
->fields('t', array_keys($schema[$table]['fields']))
->execute();
foreach ($result as $row) {
$this->assertEqual($this->entities[$row->entity_id]->{$field->name}->value, $row->$column);
}
// There are 0 entities of this bundle with non-deleted data.
$found = $factory->get('entity_test')
->condition('type', $bundle)
@ -198,20 +210,8 @@ class BulkDeleteTest extends FieldUnitTestBase {
->condition("$field_name.deleted", 1)
->sort('id')
->execute();
$ids = (object) array(
'entity_type' => 'entity_test',
'bundle' => $bundle,
);
$entities = array();
foreach ($found as $entity_id) {
$ids->entity_id = $entity_id;
$entities[$entity_id] = _field_create_entity_from_ids($ids);
}
field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('instance' => $instance));
$this->assertEqual(count($found), 10, 'Correct number of entities found after deleting');
foreach ($entities as $id => $entity) {
$this->assertEqual($this->entities[$id]->{$field->id()}->value, $entity->{$field->id()}[Language::LANGCODE_NOT_SPECIFIED][0]['value'], "Entity $id with deleted data loaded correctly");
}
$this->assertFalse(array_diff($found, array_keys($this->entities)));
}
/**
@ -226,7 +226,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
$field = reset($this->fields);
// Delete the instance.
$instance = field_info_instance($this->entity_type, $field->id(), $bundle);
$instance = field_info_instance($this->entity_type, $field->name, $bundle);
$instance->delete();
// No field hooks were called.
@ -241,19 +241,18 @@ class BulkDeleteTest extends FieldUnitTestBase {
// There are $count deleted entities left.
$found = \Drupal::entityQuery('entity_test')
->condition('type', $bundle)
->condition($field->id() . '.deleted', 1)
->condition($field->name . '.deleted', 1)
->execute();
$this->assertEqual(count($found), $count, 'Correct number of entities found after purging 2');
}
// Check hooks invocations.
// hook_field_load() and hook_field_delete() should have been called once
// for each entity in the bundle.
// hook_field_delete() should have been called once for each entity in the
// bundle.
$actual_hooks = field_test_memorize();
$hooks = array();
$entities = $this->entities_by_bundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_load'][] = array($id => $entity);
$hooks['field_test_field_delete'][] = $entity;
}
$this->checkHooksInvocations($hooks, $actual_hooks);
@ -286,7 +285,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
// Delete the first instance.
$bundle = reset($this->bundles);
$instance = field_info_instance($this->entity_type, $field->id(), $bundle);
$instance = field_info_instance($this->entity_type, $field->name, $bundle);
$instance->delete();
// Assert that hook_field_delete() was not called yet.
@ -297,13 +296,12 @@ class BulkDeleteTest extends FieldUnitTestBase {
field_purge_batch(10);
// Check hooks invocations.
// hook_field_load() and hook_field_delete() should have been called once
// for each entity in the bundle.
// hook_field_delete() should have been called once for each entity in the
// bundle.
$actual_hooks = field_test_memorize();
$hooks = array();
$entities = $this->entities_by_bundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_load'][] = array($id => $entity);
$hooks['field_test_field_delete'][] = $entity;
}
$this->checkHooksInvocations($hooks, $actual_hooks);
@ -317,7 +315,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
// Delete the second instance.
$bundle = next($this->bundles);
$instance = field_info_instance($this->entity_type, $field->id(), $bundle);
$instance = field_info_instance($this->entity_type, $field->name, $bundle);
$instance->delete();
// Assert that hook_field_delete() was not called yet.
@ -332,7 +330,6 @@ class BulkDeleteTest extends FieldUnitTestBase {
$hooks = array();
$entities = $this->entities_by_bundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_load'][] = array($id => $entity);
$hooks['field_test_field_delete'][] = $entity;
}
$this->checkHooksInvocations($hooks, $actual_hooks);

View File

@ -7,7 +7,6 @@
namespace Drupal\field\Tests;
use Drupal\Core\Language\Language;
use Drupal\field\FieldException;
class CrudTest extends FieldUnitTestBase {
@ -37,14 +36,15 @@ class CrudTest extends FieldUnitTestBase {
*/
function testCreateField() {
$field_definition = array(
'field_name' => 'field_2',
'name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
field_test_memorize();
$field = entity_create('field_entity', $field_definition);
$field->save();
$mem = field_test_memorize();
$this->assertIdentical($mem['field_test_field_entity_create'][0][0]['field_name'], $field_definition['field_name'], 'hook_entity_create() called with correct arguments.');
$this->assertIdentical($mem['field_test_field_entity_create'][0][0]['field_name'], $field_definition['name'], 'hook_entity_create() called with correct arguments.');
$this->assertIdentical($mem['field_test_field_entity_create'][0][0]['type'], $field_definition['type'], 'hook_entity_create() called with correct arguments.');
// Read the configuration. Check against raw configuration data rather than
@ -53,7 +53,9 @@ class CrudTest extends FieldUnitTestBase {
$field_config = \Drupal::config('field.field.' . $field->id())->get();
// Ensure that basic properties are preserved.
$this->assertEqual($field_config['id'], $field_definition['field_name'], 'The field name is properly saved.');
$this->assertEqual($field_config['name'], $field_definition['name'], 'The field name is properly saved.');
$this->assertEqual($field_config['entity_type'], $field_definition['entity_type'], 'The field entity type is properly saved.');
$this->assertEqual($field_config['id'], $field_definition['entity_type'] . '.' . $field_definition['name'], 'The field id is properly saved.');
$this->assertEqual($field_config['type'], $field_definition['type'], 'The field type is properly saved.');
// Ensure that cardinality defaults to 1.
@ -63,9 +65,6 @@ class CrudTest extends FieldUnitTestBase {
$field_type = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($field_definition['type']);
$this->assertEqual($field_config['settings'], $field_type['settings'], 'Default field settings have been written.');
// Ensure that default storage was set.
$this->assertEqual($field_config['storage']['type'], \Drupal::config('field.settings')->get('default_storage'), 'The field type is properly saved.');
// Guarantee that the name is unique.
try {
entity_create('field_entity', $field_definition)->save();
@ -78,7 +77,8 @@ class CrudTest extends FieldUnitTestBase {
// Check that field type is required.
try {
$field_definition = array(
'field_name' => 'field_1',
'name' => 'field_1',
'entity_type' => 'entity_type',
);
entity_create('field_entity', $field_definition)->save();
$this->fail(t('Cannot create a field with no type.'));
@ -90,7 +90,8 @@ class CrudTest extends FieldUnitTestBase {
// Check that field name is required.
try {
$field_definition = array(
'type' => 'test_field'
'type' => 'test_field',
'entity_type' => 'entity_test',
);
entity_create('field_entity', $field_definition)->save();
$this->fail(t('Cannot create an unnamed field.'));
@ -98,11 +99,24 @@ class CrudTest extends FieldUnitTestBase {
catch (FieldException $e) {
$this->pass(t('Cannot create an unnamed field.'));
}
// Check that entity type is required.
try {
$field_definition = array(
'name' => 'test_field',
'type' => 'test_field'
);
entity_create('field_entity', $field_definition)->save();
$this->fail('Cannot create a field without an entity type.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field without an entity type.');
}
// Check that field name must start with a letter or _.
try {
$field_definition = array(
'field_name' => '2field_2',
'name' => '2field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
@ -115,7 +129,8 @@ class CrudTest extends FieldUnitTestBase {
// Check that field name must only contain lowercase alphanumeric or _.
try {
$field_definition = array(
'field_name' => 'field#_3',
'name' => 'field#_3',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
@ -128,7 +143,8 @@ class CrudTest extends FieldUnitTestBase {
// Check that field name cannot be longer than 32 characters long.
try {
$field_definition = array(
'field_name' => '_12345678901234567890123456789012',
'name' => '_12345678901234567890123456789012',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
@ -143,7 +159,8 @@ class CrudTest extends FieldUnitTestBase {
try {
$field_definition = array(
'type' => 'test_field',
'field_name' => 'id',
'name' => 'id',
'entity_type' => 'entity_test',
);
entity_create('field_entity', $field_definition)->save();
$this->fail(t('Cannot create a field bearing the name of an entity key.'));
@ -161,7 +178,8 @@ class CrudTest extends FieldUnitTestBase {
*/
function testCreateFieldWithExplicitSchema() {
$field_definition = array(
'field_name' => 'field_2',
'name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
'schema' => array(
'dummy' => 'foobar'
@ -171,69 +189,32 @@ class CrudTest extends FieldUnitTestBase {
$this->assertEqual($field->getSchema(), $field_definition['schema']);
}
/**
* Test failure to create a field.
*/
function testCreateFieldFail() {
$field_name = 'duplicate';
$field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure'));
$field = entity_load('field_entity', $field_name);
// The field does not exist.
$this->assertFalse($field, 'The field does not exist.');
// Try to create the field.
try {
entity_create('field_entity', $field_definition)->save();
$this->assertTrue(FALSE, 'Field creation (correctly) fails.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Field creation (correctly) fails.');
}
// The field does not exist.
$field = entity_load('field_entity', $field_name);
$this->assertFalse($field, 'The field does not exist.');
}
/**
* Tests reading a single field definition.
*/
function testReadField() {
$field_definition = array(
'field_name' => 'field_1',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
// Read the field back.
$field = field_read_field($field_definition['field_name']);
$this->assertTrue($field_definition < $field, 'The field was properly read.');
}
/**
* Tests reading field definitions.
*/
function testReadFields() {
$field_definition = array(
'field_name' => 'field_1',
'name' => 'field_1',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
$field = entity_create('field_entity', $field_definition);
$field->save();
$id = $field->id();
// Check that 'single column' criteria works.
$fields = field_read_fields(array('field_name' => $field_definition['field_name']));
$this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
$fields = field_read_fields(array('id' => $id));
$this->assertTrue(count($fields) == 1 && isset($fields[$id]), 'The field was properly read.');
// Check that 'multi column' criteria works.
$fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type']));
$this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
$fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo'));
$fields = field_read_fields(array('id' => $id, 'type' => $field_definition['type']));
$this->assertTrue(count($fields) == 1 && isset($fields[$id]), 'The field was properly read.');
$fields = field_read_fields(array('name' => $field_definition['name'], 'type' => 'foo'));
$this->assertTrue(empty($fields), 'No field was found.');
// Create an instance of the field.
$instance_definition = array(
'field_name' => $field_definition['field_name'],
'field_name' => $field_definition['name'],
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
@ -246,11 +227,14 @@ class CrudTest extends FieldUnitTestBase {
function testFieldIndexes() {
// Check that indexes specified by the field type are used by default.
$field_definition = array(
'field_name' => 'field_1',
'name' => 'field_1',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
$field = field_read_field($field_definition['field_name']);
$field = entity_create('field_entity', $field_definition);
$field->save();
field_cache_clear();
$field = entity_load('field_entity', $field->id());
$schema = $field->getSchema();
$expected_indexes = array('value' => array('value'));
$this->assertEqual($schema['indexes'], $expected_indexes, 'Field type indexes saved by default');
@ -258,14 +242,17 @@ class CrudTest extends FieldUnitTestBase {
// Check that indexes specified by the field definition override the field
// type indexes.
$field_definition = array(
'field_name' => 'field_2',
'name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
'indexes' => array(
'value' => array(),
),
);
entity_create('field_entity', $field_definition)->save();
$field = field_read_field($field_definition['field_name']);
$field = entity_create('field_entity', $field_definition);
$field->save();
field_cache_clear();
$field = entity_load('field_entity', $field->id());
$schema = $field->getSchema();
$expected_indexes = array('value' => array());
$this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes override field type indexes');
@ -273,14 +260,18 @@ class CrudTest extends FieldUnitTestBase {
// Check that indexes specified by the field definition add to the field
// type indexes.
$field_definition = array(
'field_name' => 'field_3',
'name' => 'field_3',
'entity_type' => 'entity_test',
'type' => 'test_field',
'indexes' => array(
'value_2' => array('value'),
),
);
entity_create('field_entity', $field_definition)->save();
$field = field_read_field($field_definition['field_name']);
$field = entity_create('field_entity', $field_definition);
$field->save();
$id = $field->id();
field_cache_clear();
$field = entity_load('field_entity', $id);
$schema = $field->getSchema();
$expected_indexes = array('value' => array('value'), 'value_2' => array('value'));
$this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes');
@ -293,30 +284,38 @@ class CrudTest extends FieldUnitTestBase {
// TODO: Also test deletion of the data stored in the field ?
// Create two fields (so we can test that only one is deleted).
$this->field = array('field_name' => 'field_1', 'type' => 'test_field');
$this->field = array(
'name' => 'field_1',
'type' => 'test_field',
'entity_type' => 'entity_test',
);
entity_create('field_entity', $this->field)->save();
$this->another_field = array('field_name' => 'field_2', 'type' => 'test_field');
$this->another_field = array(
'name' => 'field_2',
'type' => 'test_field',
'entity_type' => 'entity_test',
);
entity_create('field_entity', $this->another_field)->save();
// Create instances for each.
$this->instance_definition = array(
'field_name' => $this->field['field_name'],
'field_name' => $this->field['name'],
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
entity_create('field_instance', $this->instance_definition)->save();
$another_instance_definition = $this->instance_definition;
$another_instance_definition['field_name'] = $this->another_field['field_name'];
$another_instance_definition['field_name'] = $this->another_field['name'];
entity_create('field_instance', $another_instance_definition)->save();
// Test that the first field is not deleted, and then delete it.
$field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
$field = field_read_field('entity_test', $this->field['name'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($field) && empty($field['deleted']), 'A new field is not marked for deletion.');
field_info_field($this->field['field_name'])->delete();
field_info_field('entity_test', $this->field['name'])->delete();
// Make sure that the field is marked as deleted when it is specifically
// loaded.
$field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
$field = field_read_field('entity_test', $this->field['name'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion.');
// Make sure that this field's instance is marked as deleted when it is
@ -325,7 +324,7 @@ class CrudTest extends FieldUnitTestBase {
$this->assertTrue(!empty($instance['deleted']), 'An instance for a deleted field is marked for deletion.');
// Try to load the field normally and make sure it does not show up.
$field = field_read_field($this->field['field_name']);
$field = field_read_field('entity_test', $this->field['name']);
$this->assertTrue(empty($field), 'A deleted field is not loaded by default.');
// Try to load the instance normally and make sure it does not show up.
@ -333,7 +332,7 @@ class CrudTest extends FieldUnitTestBase {
$this->assertTrue(empty($instance), 'An instance for a deleted field is not loaded by default.');
// Make sure the other field (and its field instance) are not deleted.
$another_field = field_read_field($this->another_field['field_name']);
$another_field = field_read_field('entity_test', $this->another_field['name']);
$this->assertTrue(!empty($another_field) && empty($another_field['deleted']), 'A non-deleted field is not marked for deletion.');
$another_instance = field_read_instance('entity_test', $another_instance_definition['field_name'], $another_instance_definition['bundle']);
$this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), 'An instance of a non-deleted field is not marked for deletion.');
@ -342,21 +341,18 @@ class CrudTest extends FieldUnitTestBase {
// write data into it.
entity_create('field_entity', $this->field)->save();
entity_create('field_instance', $this->instance_definition)->save();
$field = field_read_field($this->field['field_name']);
$field = field_read_field('entity_test', $this->field['name']);
$this->assertTrue(!empty($field) && empty($field['deleted']), 'A new field with a previously used name is created.');
$instance = field_read_instance('entity_test', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertTrue(!empty($instance) && empty($instance['deleted']), 'A new instance for a previously used field name is created.');
// Save an entity with data for the field
$entity = entity_create('entity_test', array('id' => 0, 'revision_id' => 0));
$langcode = Language::LANGCODE_NOT_SPECIFIED;
$entity = entity_create('entity_test', array());
$values[0]['value'] = mt_rand(1, 127);
$entity->{$field['field_name']}->value = $values[0]['value'];
field_attach_insert($entity);
$entity = $this->entitySaveReload($entity);
// Verify the field is present on load
$entity = entity_create('entity_test', array('id' => 0, 'revision_id' => 0));
field_attach_load('entity_test', array(0 => $entity));
$this->assertIdentical(count($entity->{$field['field_name']}), count($values), "Data in previously deleted field saves and loads correctly");
foreach ($values as $delta => $value) {
$this->assertEqual($entity->{$field['field_name']}[$delta]->value, $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
@ -364,7 +360,11 @@ class CrudTest extends FieldUnitTestBase {
}
function testUpdateFieldType() {
$field_definition = array('field_name' => 'field_type', 'type' => 'number_decimal');
$field_definition = array(
'name' => 'field_type',
'entity_type' => 'entity_test',
'type' => 'number_decimal',
);
$field = entity_create('field_entity', $field_definition);
$field->save();
@ -387,7 +387,8 @@ class CrudTest extends FieldUnitTestBase {
// systems, it makes a good test case.
$cardinality = 4;
$field = entity_create('field_entity', array(
'field_name' => 'field_update',
'name' => 'field_update',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => $cardinality,
));
@ -400,22 +401,18 @@ class CrudTest extends FieldUnitTestBase {
$instance->save();
do {
// We need a unique ID for our entity. $cardinality will do.
$id = $cardinality;
$entity = entity_create('entity_test', array('id' => $id, 'revision_id' => $id));
$entity = entity_create('entity_test', array());
// Fill in the entity with more values than $cardinality.
for ($i = 0; $i < 20; $i++) {
$entity->field_update[$i]->value = $i;
// We can not use $i here because 0 values are filtered out.
$entity->field_update[$i]->value = $i + 1;
}
// Save the entity.
field_attach_insert($entity);
// Load back and assert there are $cardinality number of values.
$entity = entity_create('entity_test', array('id' => $id, 'revision_id' => $id));
field_attach_load('entity_test', array($id => $entity));
$this->assertEqual(count($entity->field_update), $field->cardinality, 'Cardinality is kept');
$entity = $this->entitySaveReload($entity);
$this->assertEqual(count($entity->field_update), $field->cardinality);
// Now check the values themselves.
for ($delta = 0; $delta < $cardinality; $delta++) {
$this->assertEqual($entity->field_update[$delta]->value, $delta, 'Value is kept');
$this->assertEqual($entity->field_update[$delta]->value, $delta + 1);
}
// Increase $cardinality and set the field cardinality to the new value.
$field->cardinality = ++$cardinality;
@ -428,7 +425,8 @@ class CrudTest extends FieldUnitTestBase {
*/
function testUpdateFieldForbid() {
$field = entity_create('field_entity', array(
'field_name' => 'forbidden',
'name' => 'forbidden',
'entity_type' => 'entity_test',
'type' => 'test_field',
'settings' => array(
'changeable' => 0,

View File

@ -70,7 +70,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$this->cardinality = 4;
$field = array(
'field_name' => $this->field_name,
'name' => $this->field_name,
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => $this->cardinality,
);

View File

@ -52,12 +52,13 @@ class FieldAccessTest extends FieldTestBase {
$content_type = $content_type_info->type;
$field = array(
'field_name' => 'test_view_field',
'name' => 'test_view_field',
'entity_type' => 'node',
'type' => 'text',
);
entity_create('field_entity', $field)->save();
$instance = array(
'field_name' => $field['field_name'],
'field_name' => $field['name'],
'entity_type' => 'node',
'bundle' => $content_type,
);
@ -66,7 +67,7 @@ class FieldAccessTest extends FieldTestBase {
// Assign display properties for the 'default' and 'teaser' view modes.
foreach (array('default', 'teaser') as $view_mode) {
entity_get_display('node', $content_type, $view_mode)
->setComponent($field['field_name'])
->setComponent($field['name'])
->save();
}

View File

@ -236,7 +236,7 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
*/
function testFieldAttachCache() {
// Initialize random values and a test entity.
$entity_init = entity_create('entity_test', array('id' => 1, 'revision_id' => 1, 'type' => $this->instance['bundle']));
$entity_init = entity_create('entity_test', array('type' => $this->instance['bundle']));
$langcode = Language::LANGCODE_NOT_SPECIFIED;
$values = $this->_generateTestFieldValues($this->field['cardinality']);
@ -250,87 +250,72 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
$entity->{$this->field_name}->setValue($values);
field_attach_insert($entity);
$this->assertFalse(cache('field')->get($cid), 'Non-cached: no cache entry on insert');
// Load, and check that no cache entry is present.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$this->assertFalse(cache('field')->get($cid), 'Non-cached: no cache entry on load');
$entity = $this->entitySaveReload($entity);
$cid = "field:$entity_type:" . $entity->id();
$this->assertFalse(cache('field')->get($cid), 'Non-cached: no cache entry on insert and load');
// Cacheable entity type.
$entity_type = 'entity_test_cache';
$cid = "field:$entity_type:" . $entity_init->id();
$instance_definition = $this->instance_definition;
$instance_definition['entity_type'] = $entity_type;
$instance_definition['bundle'] = $entity_type;
entity_create('field_instance', $instance_definition)->save();
$this->createFieldWithInstance('_2', 'entity_test_cache');
entity_info_cache_clear();
$entity_init = entity_create($entity_type, array(
'id' => 1,
'revision_id' => 1,
'type' => $entity_type,
));
// Check that no initial cache entry is present.
$cid = "field:$entity_type:" . $entity->id();
$this->assertFalse(cache('field')->get($cid), 'Cached: no initial cache entry');
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
$entity->{$this->field_name} = $values;
field_attach_insert($entity);
$entity->{$this->field_name_2} = $values;
$entity->save();
$cid = "field:$entity_type:" . $entity->id();
$this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry on insert');
// Load a single field, and check that no cache entry is present.
$entity = clone($entity_init);
$instance = field_info_instance($entity->entityType(), $this->field_name, $entity->bundle());
field_attach_load($entity_type, array($entity->id() => $entity), FIELD_LOAD_CURRENT, array('instance' => $instance));
$cache = cache('field')->get($cid);
$this->assertFalse($cache, 'Cached: no cache entry on loading a single field');
// Load, and check that a cache entry is present with the expected values.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$controller = $this->container->get('plugin.manager.entity')->getStorageController($entity->entityType());
$controller->resetCache();
$controller->load($entity->id());
$cache = cache('field')->get($cid);
$this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load');
$this->assertEqual($cache->data[$this->field_name_2][$langcode], $values, 'Cached: correct cache entry on load');
// Update with different values, and check that the cache entry is wiped.
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity = clone($entity_init);
$entity->{$this->field_name} = $values;
field_attach_update($entity);
$values = $this->_generateTestFieldValues($this->field_name_2['cardinality']);
$entity = entity_create($entity_type, array(
'type' => $entity_type,
'id' => $entity->id(),
));
$entity->{$this->field_name_2} = $values;
$entity->save();
$this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry on update');
// Load, and check that a cache entry is present with the expected values.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$controller->resetCache();
$controller->load($entity->id());
$cache = cache('field')->get($cid);
$this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load');
$this->assertEqual($cache->data[$this->field_name_2][$langcode], $values, 'Cached: correct cache entry on load');
// Create a new revision, and check that the cache entry is wiped.
$entity_init = entity_create($entity_type, array(
'id' => 1,
'revision_id' => 2,
$entity = entity_create($entity_type, array(
'type' => $entity_type,
'id' => $entity->id(),
));
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues($this->field_name_2['cardinality']);
$entity->{$this->field_name} = $values;
field_attach_update($entity);
$cache = cache('field')->get($cid);
$entity->setNewRevision();
$entity->save();
$this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry on new revision creation');
// Load, and check that a cache entry is present with the expected values.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$controller->resetCache();
$controller->load($entity->id());
$cache = cache('field')->get($cid);
$this->assertEqual($cache->data[$this->field_name][$langcode], $values, 'Cached: correct cache entry on load');
$this->assertEqual($cache->data[$this->field_name_2][$langcode], $values, 'Cached: correct cache entry on load');
// Delete, and check that the cache entry is wiped.
field_attach_delete($entity);
$entity->delete();
$this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry after delete');
}

View File

@ -62,26 +62,22 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
// TODO : test empty values filtering and "compression" (store consecutive deltas).
// Preparation: create three revisions and store them in $revision array.
$values = array();
$entity = entity_create($entity_type, array());
for ($revision_id = 0; $revision_id < 3; $revision_id++) {
$revision[$revision_id] = entity_create($entity_type, array('id' => 0, 'revision_id' => $revision_id));
// Note: we try to insert one extra value.
$values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
$current_revision = $revision_id;
// If this is the first revision do an insert.
if (!$revision_id) {
$revision[$revision_id]->{$this->field_name}->setValue($values[$revision_id]);
field_attach_insert($revision[$revision_id]);
}
else {
// Otherwise do an update.
$revision[$revision_id]->{$this->field_name}->setValue($values[$revision_id]);
field_attach_update($revision[$revision_id]);
}
$current_values = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
$entity->{$this->field_name}->setValue($current_values);
$entity->setNewRevision();
$entity->save();
$entity_id = $entity->id();
$current_revision = $entity->getRevisionId();
$values[$current_revision] = $current_values;
}
$storage_controller = $this->container->get('plugin.manager.entity')->getStorageController($entity_type);
$storage_controller->resetCache();
$entity = $storage_controller->load($entity_id);
// Confirm current revision loads the correct data.
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0));
field_attach_load($entity_type, array(0 => $entity));
// Number of values per field loaded equals the field cardinality.
$this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], 'Current revision: expected number of values');
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
@ -92,9 +88,8 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
}
// Confirm each revision loads the correct data.
foreach (array_keys($revision) as $revision_id) {
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => $revision_id));
field_attach_load_revision($entity_type, array(0 => $entity));
foreach (array_keys($values) as $revision_id) {
$entity = $storage_controller->loadRevision($revision_id);
// Number of values per field loaded equals the field cardinality.
$this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], format_string('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
@ -130,7 +125,11 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
);
for ($i = 1; $i <= 3; $i++) {
$field_names[$i] = 'field_' . $i;
$field = entity_create('field_entity', array('field_name' => $field_names[$i], 'type' => 'test_field'));
$field = entity_create('field_entity', array(
'name' => $field_names[$i],
'entity_type' => $entity_type,
'type' => 'test_field',
));
$field->save();
$field_ids[$i] = $field['uuid'];
foreach ($field_bundles_map[$i] as $bundle) {
@ -156,11 +155,14 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
$values[$index][$field_name] = mt_rand(1, 127);
$entity->$field_name->setValue(array('value' => $values[$index][$field_name]));
}
field_attach_insert($entity);
$entity->enforceIsnew();
$entity->save();
}
// Check that a single load correctly loads field values for both entities.
field_attach_load($entity_type, $entities);
$controller = $this->container->get('plugin.manager.entity')->getStorageController($entity->entityType());
$controller->resetCache();
$entities = $controller->loadMultiple();
foreach ($entities as $index => $entity) {
$instances = field_info_instances($entity_type, $bundles[$index]);
foreach ($instances as $field_name => $instance) {
@ -170,165 +172,48 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
$this->assertEqual($entity->{$field_name}->additional_key, 'additional_value', format_string('Entity %index: extra information was found', array('%index' => $index)));
}
}
// Check that the single-field load option works.
$entity = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $bundles[1]));
$instance = field_info_instance($entity->entityType(), $field_names[1], $entity->bundle());
field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('instance' => $instance));
$this->assertEqual($entity->{$field_names[1]}->value, $values[1][$field_names[1]], format_string('Entity %index: expected value was found.', array('%index' => 1)));
$this->assertEqual($entity->{$field_names[1]}->additional_key, 'additional_value', format_string('Entity %index: extra information was found', array('%index' => 1)));
$this->assert(empty($entity->{$field_names[2]}->value), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
$this->assert(!isset($entity->{$field_names[3]}), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
}
/**
* Test saving and loading fields using different storage backends.
*/
function testFieldAttachSaveLoadDifferentStorage() {
$entity_type = 'entity_test';
// Create two fields using different storage backends, and their instances.
$fields = array(
array(
'field_name' => 'field_1',
'type' => 'test_field',
'cardinality' => 4,
'storage' => array('type' => 'field_sql_storage')
),
array(
'field_name' => 'field_2',
'type' => 'test_field',
'cardinality' => 4,
'storage' => array('type' => 'field_test_storage')
),
);
foreach ($fields as $field) {
entity_create('field_entity', $field)->save();
$instance = array(
'field_name' => $field['field_name'],
'entity_type' => $entity_type,
'bundle' => $entity_type,
);
entity_create('field_instance', $instance)->save();
}
$entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1));
// Create entity and insert random values.
$entity = clone($entity_init);
$values = array();
foreach ($fields as $field) {
$values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']);
$entity->{$field['field_name']} = $values[$field['field_name']];
}
field_attach_insert($entity);
// Check that values are loaded as expected.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
foreach ($fields as $field) {
$this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}->getValue(), format_string('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
}
}
/**
* Test storage details alteration.
*
* @see field_test_storage_details_alter()
*/
function testFieldStorageDetailsAlter() {
$field_name = 'field_test_change_my_details';
$field = entity_create('field_entity', array(
'field_name' => $field_name,
'type' => 'test_field',
'cardinality' => 4,
'storage' => array('type' => 'field_test_storage'),
));
$field->save();
$instance = entity_create('field_instance', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
));
$instance->save();
// The storage details are indexed by a storage engine type.
$this->assertTrue(array_key_exists('drupal_variables', $field['storage_details']), 'The storage type is Drupal variables.');
$details = $field['storage_details']['drupal_variables'];
// The field_test storage details are indexed by variable name. The details
// are altered, so moon and mars are correct for this test.
$this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), 'Moon is available in the instance array.');
$this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), 'Mars is available in the instance array.');
// Test current and revision storage details together because the columns
// are the same.
foreach ($field['columns'] as $column_name => $attributes) {
$this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]')));
$this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]')));
}
}
/**
* Tests insert and update with empty or NULL fields.
*/
function testFieldAttachSaveEmptyData() {
$entity_type = 'entity_test_rev';
$entity_type = 'entity_test';
$this->createFieldWithInstance('', $entity_type);
$entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1));
$entity_init = entity_create($entity_type, array('id' => 1));
// Insert: Field is NULL.
field_cache_clear();
$entity = clone($entity_init);
$entity = clone $entity_init;
$entity->{$this->field_name} = NULL;
field_attach_insert($entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$entity->enforceIsNew();
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
// Add some real data.
field_cache_clear();
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues(1);
$entity->{$this->field_name} = $values;
field_attach_insert($entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$entity = $this->entitySaveReload($entity);
$this->assertEqual($entity->{$this->field_name}->getValue(), $values, 'Field data saved');
// Update: Field is NULL. Data should be wiped.
field_cache_clear();
$entity = clone($entity_init);
$entity->{$this->field_name} = NULL;
field_attach_update($entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->field_name}->isEmpty(), 'Update: NULL field removes existing values');
// Re-add some data.
field_cache_clear();
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues(1);
$entity->{$this->field_name} = $values;
field_attach_update($entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$entity = $this->entitySaveReload($entity);
$this->assertEqual($entity->{$this->field_name}->getValue(), $values, 'Field data saved');
// Update: Field is empty array. Data should be wiped.
field_cache_clear();
$entity = clone($entity_init);
$entity->{$this->field_name} = array();
field_attach_update($entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->id() => $entity));
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->field_name}->isEmpty(), 'Update: empty array removes existing values');
}
@ -351,11 +236,8 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
// Insert: Field is NULL.
$entity = clone($entity_init);
$entity->getBCEntity()->{$this->field_name} = NULL;
field_attach_insert($entity);
$entity = clone($entity_init);
$entity->getBCEntity()->{$this->field_name} = array();
field_attach_load($entity_type, array($entity->id() => $entity));
$entity->enforceIsNew();
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
// Verify that prepopulated field values are not overwritten by defaults.
@ -365,58 +247,60 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
}
/**
* Test field_attach_delete().
* Test entity deletion.
*/
function testFieldAttachDelete() {
$entity_type = 'entity_test_rev';
$this->createFieldWithInstance('', $entity_type);
$rev[0] = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
$entity = entity_create($entity_type, array('type' => $this->instance['bundle']));
$vids = array();
// Create revision 0
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$rev[0]->{$this->field_name} = $values;
field_attach_insert($rev[0]);
$entity->{$this->field_name} = $values;
$entity->save();
$vids[] = $entity->getRevisionId();
// Create revision 1
$rev[1] = entity_create($entity_type, array('id' => 0, 'revision_id' => 1, 'type' => $this->instance['bundle']));
$rev[1]->{$this->field_name} = $values;
field_attach_update($rev[1]);
$entity->setNewRevision();
$entity->save();
$vids[] = $entity->getRevisionId();
// Create revision 2
$rev[2] = entity_create($entity_type, array('id' => 0, 'revision_id' => 2, 'type' => $this->instance['bundle']));
$rev[2]->{$this->field_name} = $values;
field_attach_update($rev[2]);
$entity->setNewRevision();
$entity->save();
$vids[] = $entity->getRevisionId();
$controller = $this->container->get('plugin.manager.entity')->getStorageController($entity->entityType());
$controller->resetCache();
// Confirm each revision loads
foreach (array_keys($rev) as $vid) {
$read = entity_create($entity_type, array('id' => 0, 'revision_id' => $vid, 'type' => $this->instance['bundle']));
field_attach_load_revision($entity_type, array(0 => $read));
$this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values.");
foreach ($vids as $vid) {
$revision = $controller->loadRevision($vid);
$this->assertEqual(count($revision->{$this->field_name}), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values.");
}
// Delete revision 1, confirm the other two still load.
field_attach_delete_revision($rev[1]);
foreach (array(0, 2) as $vid) {
$read = entity_create($entity_type, array('id' => 0, 'revision_id' => $vid, 'type' => $this->instance['bundle']));
field_attach_load_revision($entity_type, array(0 => $read));
$this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values.");
$controller->deleteRevision($vids[1]);
$controller->resetCache();
foreach (array(0, 2) as $key) {
$vid = $vids[$key];
$revision = $controller->loadRevision($vid);
$this->assertEqual(count($revision->{$this->field_name}), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values.");
}
// Confirm the current revision still loads
$read = entity_create($entity_type, array('id' => 0, 'revision_id' => 2, 'type' => $this->instance['bundle']));
field_attach_load($entity_type, array(0 => $read));
$this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test entity current revision has {$this->field['cardinality']} values.");
$controller->resetCache();
$current = $controller->load($entity->id());
$this->assertEqual(count($current->{$this->field_name}), $this->field['cardinality'], "The test entity current revision has {$this->field['cardinality']} values.");
// Delete all field data, confirm nothing loads
field_attach_delete($rev[2]);
$entity->delete();
$controller->resetCache();
foreach (array(0, 1, 2) as $vid) {
$read = entity_create($entity_type, array('id' => 0, 'revision_id' => $vid, 'type' => $this->instance['bundle']));
field_attach_load_revision($entity_type, array(0 => $read));
$this->assertIdentical($read->{$this->field_name}[0]->getValue(), array(), "The test entity revision $vid is deleted.");
$revision = $controller->loadRevision($vid);
$this->assertFalse($revision);
}
$read = entity_create($entity_type, array('id' => 0, 'revision_id' => 2, 'type' => $this->instance['bundle']));
field_attach_load($entity_type, array(0 => $read));
$this->assertIdentical($read->{$this->field_name}[0]->getValue(), array(), 'The test entity current revision is deleted.');
$this->assertFalse($controller->load($entity->id()));
}
/**
@ -435,14 +319,12 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
entity_create('field_instance', $this->instance_definition)->save();
// Save an entity with data in the field.
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
$entity = entity_create($entity_type, array('type' => $this->instance['bundle']));
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity->{$this->field_name} = $values;
field_attach_insert($entity);
// Verify the field data is present on load.
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
field_attach_load($entity_type, array(0 => $entity));
$entity = $this->entitySaveReload($entity);
$this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data is retrieved for the new bundle");
// Rename the bundle.
@ -454,8 +336,9 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
$this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
// Verify the field data is present on load.
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
field_attach_load($entity_type, array(0 => $entity));
$controller = $this->container->get('plugin.manager.entity')->getStorageController($entity->entityType());
$controller->resetCache();
$entity = $controller->load($entity->id());
$this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage");
}
@ -476,7 +359,12 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
// Create a second field for the test bundle
$field_name = drupal_strtolower($this->randomName() . '_field_name');
$field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1);
$field = array(
'name' => $field_name,
'entity_type' => $entity_type,
'type' => 'test_field',
'cardinality' => 1,
);
entity_create('field_entity', $field)->save();
$instance = array(
'field_name' => $field_name,
@ -489,15 +377,13 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
entity_create('field_instance', $instance)->save();
// Save an entity with data for both fields
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
$entity = entity_create($entity_type, array('type' => $this->instance['bundle']));
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity->{$this->field_name} = $values;
$entity->{$field_name} = $this->_generateTestFieldValues(1);
field_attach_insert($entity);
$entity = $this->entitySaveReload($entity);
// Verify the fields are present on load
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
field_attach_load($entity_type, array(0 => $entity));
$this->assertEqual(count($entity->{$this->field_name}), 4, 'First field got loaded');
$this->assertEqual(count($entity->{$field_name}), 1, 'Second field got loaded');
@ -505,8 +391,10 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
entity_test_delete_bundle($this->instance['bundle'], $entity_type);
// Verify no data gets loaded
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0, 'type' => $this->instance['bundle']));
field_attach_load($entity_type, array(0 => $entity));
$controller = $this->container->get('plugin.manager.entity')->getStorageController($entity->entityType());
$controller->resetCache();
$entity= $controller->load($entity->id());
$this->assertTrue(empty($entity->{$this->field_name}), 'No data for first field');
$this->assertTrue(empty($entity->{$field_name}), 'No data for second field');

View File

@ -26,11 +26,13 @@ class FieldImportCreateTest extends FieldUnitTestBase {
* Tests creating fields and instances during default config import.
*/
function testImportCreateDefault() {
$field_id = 'field_test_import';
$instance_id = "entity_test.entity_test.$field_id";
$field_id_2 = 'field_test_import_2';
$instance_id_2a = "entity_test.entity_test.$field_id_2";
$instance_id_2b = "entity_test.entity_test.$field_id_2";
$field_name = 'field_test_import';
$field_id = "entity_test.$field_name";
$instance_id = "entity_test.entity_test.$field_name";
$field_name_2 = 'field_test_import_2';
$field_id_2 = "entity_test.$field_name_2";
$instance_id_2a = "entity_test.entity_test.$field_name_2";
$instance_id_2b = "entity_test.test_bundle.$field_name_2";
// Check that the fields and instances do not exist yet.
$this->assertFalse(entity_load('field_entity', $field_id));
@ -72,15 +74,17 @@ class FieldImportCreateTest extends FieldUnitTestBase {
*/
function testImportCreate() {
// One field with one field instance.
$field_id = 'field_test_import_staging';
$instance_id = "entity_test.entity_test.$field_id";
$field_name = 'field_test_import_staging';
$field_id = "entity_test.$field_name";
$instance_id = "entity_test.entity_test.$field_name";
$field_config_name = "field.field.$field_id";
$instance_config_name = "field.instance.$instance_id";
// One field with two field instances.
$field_id_2 = 'field_test_import_staging_2';
$instance_id_2a = "entity_test.test_bundle.$field_id_2";
$instance_id_2b = "entity_test.test_bundle_2.$field_id_2";
$field_name_2 = 'field_test_import_staging_2';
$field_id_2 = "entity_test.$field_name_2";
$instance_id_2a = "entity_test.test_bundle.$field_name_2";
$instance_id_2b = "entity_test.test_bundle_2.$field_name_2";
$field_config_name_2 = "field.field.$field_id_2";
$instance_config_name_2a = "field.instance.$instance_id_2a";
$instance_config_name_2b = "field.instance.$instance_id_2b";

View File

@ -31,11 +31,13 @@ class FieldImportDeleteTest extends FieldUnitTestBase {
* Tests deleting fields and instances as part of config import.
*/
public function testImportDelete() {
$field_id = 'field_test_import';
$field_id_2 = 'field_test_import_2';
$instance_id = "entity_test.test_bundle.$field_id";
$instance_id_2a = "entity_test.test_bundle.$field_id_2";
$instance_id_2b = "entity_test.test_bundle_2.$field_id_2";
$field_name = 'field_test_import';
$field_id = "entity_test.$field_name";
$field_name_2 = 'field_test_import_2';
$field_id_2 = "entity_test.$field_name_2";
$instance_id = "entity_test.test_bundle.$field_name";
$instance_id_2a = "entity_test.test_bundle.$field_name_2";
$instance_id_2b = "entity_test.test_bundle_2.$field_name_2";
$field_config_name = "field.field.$field_id";
$field_config_name_2 = "field.field.$field_id_2";
$instance_config_name = "field.instance.$instance_id";

View File

@ -32,15 +32,6 @@ class FieldInfoTest extends FieldUnitTestBase {
$this->assertEqual($info[$t_key]['provider'], 'field_test', 'Field type field_test module appears.');
}
$storage_info = field_test_field_storage_info();
$info = field_info_storage_types();
foreach ($storage_info as $s_key => $storage) {
foreach ($storage as $key => $val) {
$this->assertEqual($info[$s_key][$key], $val, format_string('Storage type %s_key key %key is %value', array('%s_key' => $s_key, '%key' => $key, '%value' => print_r($val, TRUE))));
}
$this->assertEqual($info[$s_key]['module'], 'field_test', 'Storage type field_test module appears.');
}
// Verify that no unexpected instances exist.
$instances = field_info_instances('entity_test');
$expected = array();
@ -51,25 +42,26 @@ class FieldInfoTest extends FieldUnitTestBase {
// Create a field, verify it shows up.
$core_fields = field_info_fields();
$field = entity_create('field_entity', array(
'field_name' => drupal_strtolower($this->randomName()),
'name' => drupal_strtolower($this->randomName()),
'entity_type' => 'entity_test',
'type' => 'test_field',
));
$field->save();
$fields = field_info_fields();
$this->assertEqual(count($fields), count($core_fields) + 1, 'One new field exists');
$this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], 'info fields contains field name');
$this->assertEqual($fields[$field['field_name']]['type'], $field['type'], 'info fields contains field type');
$this->assertEqual($fields[$field['field_name']]['module'], 'field_test', 'info fields contains field module');
$this->assertEqual($fields[$field['uuid']]['field_name'], $field['field_name'], 'info fields contains field name');
$this->assertEqual($fields[$field['uuid']]['type'], $field['type'], 'info fields contains field type');
$this->assertEqual($fields[$field['uuid']]['module'], 'field_test', 'info fields contains field module');
$settings = array('test_field_setting' => 'dummy test string');
foreach ($settings as $key => $val) {
$this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, format_string('Field setting %key has correct default value %value', array('%key' => $key, '%value' => $val)));
$this->assertEqual($fields[$field['uuid']]['settings'][$key], $val, format_string('Field setting %key has correct default value %value', array('%key' => $key, '%value' => $val)));
}
$this->assertEqual($fields[$field['field_name']]['cardinality'], 1, 'info fields contains cardinality 1');
$this->assertEqual($fields[$field['field_name']]['active'], TRUE, 'info fields contains active 1');
$this->assertEqual($fields[$field['uuid']]['cardinality'], 1, 'info fields contains cardinality 1');
$this->assertEqual($fields[$field['uuid']]['active'], TRUE, 'info fields contains active 1');
// Create an instance, verify that it shows up
$instance_definition = array(
'field_name' => $field['field_name'],
'field_name' => $field['name'],
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'label' => $this->randomName(),
@ -121,7 +113,8 @@ class FieldInfoTest extends FieldUnitTestBase {
*/
function testFieldPrepare() {
$field_definition = array(
'field_name' => 'field',
'name' => 'field',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$field = entity_create('field_entity', $field_definition);
@ -136,7 +129,7 @@ class FieldInfoTest extends FieldUnitTestBase {
field_info_cache_clear();
// Read the field back.
$field = field_info_field($field_definition['field_name']);
$field = field_info_field('entity_test', $field_definition['name']);
// Check that all expected settings are in place.
$field_type = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($field_definition['type']);
@ -148,12 +141,13 @@ class FieldInfoTest extends FieldUnitTestBase {
*/
function testInstancePrepare() {
$field_definition = array(
'field_name' => 'field',
'name' => 'field',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
$instance_definition = array(
'field_name' => $field_definition['field_name'],
'field_name' => $field_definition['name'],
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
@ -182,8 +176,10 @@ class FieldInfoTest extends FieldUnitTestBase {
function testInstanceDisabledEntityType() {
// For this test the field type and the entity type must be exposed by
// different modules.
$this->enableModules(array('node', 'comment'));
$field_definition = array(
'field_name' => 'field',
'name' => 'field',
'entity_type' => 'comment',
'type' => 'test_field',
);
entity_create('field_entity', $field_definition)->save();
@ -192,7 +188,7 @@ class FieldInfoTest extends FieldUnitTestBase {
'entity_type' => 'comment',
'bundle' => 'comment_node_article',
);
entity_create('field_instance', $instance_definition)->save();
entity_create('field_instance', $instance_definition);
// Disable coment module. This clears field_info cache.
module_disable(array('comment'));
@ -212,11 +208,18 @@ class FieldInfoTest extends FieldUnitTestBase {
// Create a couple fields.
$fields = array(
array(
'field_name' => 'field_1',
'name' => 'field_1',
'entity_type' => 'entity_test',
'type' => 'test_field',
),
array(
'field_name' => 'field_2',
'name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'hidden_test_field',
),
array(
'name' => 'field_2',
'entity_type' => 'entity_test_cache',
'type' => 'hidden_test_field',
),
);
@ -252,17 +255,20 @@ class FieldInfoTest extends FieldUnitTestBase {
}
$expected = array(
'field_1' => array(
'type' => 'test_field',
'bundles' => array(
'entity_test' => array('entity_test', 'test_bundle_2'),
'entity_test' => array(
'field_1' => array(
'type' => 'test_field',
'bundles' => array('entity_test', 'test_bundle_2'),
),
'field_2' => array(
'type' => 'hidden_test_field',
'bundles' => array('entity_test'),
),
),
'field_2' => array(
'type' => 'hidden_test_field',
'bundles' => array(
'entity_test' => array('entity_test'),
'entity_test_cache' => array('entity_test'),
'entity_test_cache' => array(
'field_2' => array(
'type' => 'hidden_test_field',
'bundles' => array('entity_test')
),
),
);
@ -293,12 +299,13 @@ class FieldInfoTest extends FieldUnitTestBase {
// field_info_fields().
$field_name = drupal_strtolower($this->randomName());
$field = entity_create('field_entity', array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'test_field',
));
$field->save();
$fields = field_info_fields();
$this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().');
$this->assertTrue(isset($fields[$field->uuid]), 'The test field is initially found in the array returned by field_info_fields().');
// Now rebuild the field info cache, and set a variable which will cause
// the cache to be cleared while it's being rebuilt; see
@ -307,7 +314,7 @@ class FieldInfoTest extends FieldUnitTestBase {
field_info_cache_clear();
\Drupal::state()->set('field_test.clear_info_cache_in_hook_entity_info', TRUE);
$fields = field_info_fields();
$this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.');
$this->assertTrue(isset($fields[$field->uuid]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.');
}
/**

View File

@ -44,13 +44,14 @@ class FieldInstanceCrudTest extends FieldUnitTestBase {
parent::setUp();
$this->field_definition = array(
'field_name' => drupal_strtolower($this->randomName()),
'name' => drupal_strtolower($this->randomName()),
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$this->field = entity_create('field_entity', $this->field_definition);
$this->field->save();
$this->instance_definition = array(
'field_name' => $this->field['field_name'],
'field_name' => $this->field->name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
@ -103,40 +104,6 @@ class FieldInstanceCrudTest extends FieldUnitTestBase {
$this->pass(t('Cannot create an instance of a non-existing field.'));
}
// Create a field restricted to a specific entity type.
$field_restricted_definition = array(
'field_name' => drupal_strtolower($this->randomName()),
'type' => 'test_field',
'entity_types' => array('entity_test_cache'),
);
$field_restricted = entity_create('field_entity', $field_restricted_definition);
$field_restricted->save();
// Check that an instance can be added to an entity type allowed
// by the field.
try {
$instance = $this->instance_definition;
$instance['field_name'] = $field_restricted_definition['field_name'];
$instance['entity_type'] = 'entity_test_cache';
entity_create('field_instance', $instance)->save();
$this->pass(t('Can create an instance on an entity type allowed by the field.'));
}
catch (FieldException $e) {
$this->fail(t('Can create an instance on an entity type allowed by the field.'));
}
// Check that an instance cannot be added to an entity type
// forbidden by the field.
try {
$instance = $this->instance_definition;
$instance['field_name'] = $field_restricted_definition['field_name'];
entity_create('field_instance', $instance)->save();
$this->fail(t('Cannot create an instance on an entity type forbidden by the field.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create an instance on an entity type forbidden by the field.'));
}
// TODO: test other failures.
}
@ -212,7 +179,7 @@ class FieldInstanceCrudTest extends FieldUnitTestBase {
$another_instance->delete();
$deleted_fields = \Drupal::state()->get('field.field.deleted');
$this->assertTrue(isset($deleted_fields[$another_instance['field_id']]), 'A deleted field is marked for deletion.');
$field = field_read_field($another_instance['field_name']);
$field = field_read_field($another_instance['entity_type'], $another_instance['field_name']);
$this->assertFalse($field, 'The field marked to be deleted is not found anymore in the configuration.');
}

View File

@ -20,7 +20,7 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('user', 'entity', 'system', 'field', 'text', 'field_sql_storage', 'entity_test', 'field_test');
public static $modules = array('user', 'entity', 'system', 'field', 'text', 'entity_test', 'field_test');
/**
* A string for assert raw and text helper methods.
@ -65,7 +65,12 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
$instance_definition = 'instance_definition' . $suffix;
$this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix);
$this->$field = entity_create('field_entity', array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4));
$this->$field = entity_create('field_entity', array(
'name' => $this->$field_name,
'entity_type' => $entity_type,
'type' => 'test_field',
'cardinality' => 4,
));
$this->$field->save();
$this->$field_id = $this->{$field}['uuid'];
$this->$instance_definition = array(
@ -91,6 +96,22 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
->save();
}
/**
* Saves and reloads an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to save.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity, freshly reloaded from storage.
*/
protected function entitySaveReload(EntityInterface $entity) {
$entity->save();
$controller = $this->container->get('plugin.manager.entity')->getStorageController($entity->entityType());
$controller->resetCache();
return $controller->load($entity->id());
}
/**
* Generate random values for a field_test field.
*
@ -125,9 +146,13 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
* (Optional) the name of the column to check.
*/
function assertFieldValues(EntityInterface $entity, $field_name, $langcode, $expected_values, $column = 'value') {
$e = clone $entity;
field_attach_load('entity_test', array($e->id() => $e));
$values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array();
// Re-load the entity to make sure we have the latest changes.
entity_get_controller($entity->entityType())->resetCache(array($entity->id()));
$e = entity_load($entity->entityType(), $entity->id());
$field = $values = $e->getTranslation($langcode)->$field_name;
// Filter out empty values so that they don't mess with the assertions.
$field->filterEmptyValues();
$values = $field->getValue();
$this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
foreach ($expected_values as $key => $value) {
$this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', array('@value' => $value)));

View File

@ -61,9 +61,23 @@ class FormTest extends FieldTestBase {
$web_user = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
$this->drupalLogin($web_user);
$this->field_single = array('field_name' => 'field_single', 'type' => 'test_field');
$this->field_multiple = array('field_name' => 'field_multiple', 'type' => 'test_field', 'cardinality' => 4);
$this->field_unlimited = array('field_name' => 'field_unlimited', 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED);
$this->field_single = array(
'name' => 'field_single',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$this->field_multiple = array(
'name' => 'field_multiple',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => 4,
);
$this->field_unlimited = array(
'name' => 'field_unlimited',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
);
$this->instance = array(
'entity_type' => 'entity_test',
@ -79,7 +93,7 @@ class FormTest extends FieldTestBase {
function testFieldFormSingle() {
$field = $this->field_single;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
entity_create('field_entity', $field)->save();
entity_create('field_instance', $this->instance)->save();
@ -162,7 +176,7 @@ class FormTest extends FieldTestBase {
*/
function testFieldFormDefaultValue() {
$field = $this->field_single;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
$default = rand(1, 127);
$this->instance['default_value'] = array(array('value' => $default));
@ -194,7 +208,7 @@ class FormTest extends FieldTestBase {
function testFieldFormSingleRequired() {
$field = $this->field_single;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
$this->instance['required'] = TRUE;
entity_create('field_entity', $field)->save();
@ -244,7 +258,7 @@ class FormTest extends FieldTestBase {
function testFieldFormUnlimited() {
$field = $this->field_unlimited;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
entity_create('field_entity', $field)->save();
entity_create('field_instance', $this->instance)->save();
@ -331,7 +345,7 @@ class FormTest extends FieldTestBase {
function testFieldFormMultivalueWithRequiredRadio() {
// Create a multivalue test field.
$field = $this->field_unlimited;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
entity_create('field_entity', $field)->save();
entity_create('field_instance', $this->instance)->save();
@ -342,7 +356,8 @@ class FormTest extends FieldTestBase {
// Add a required radio field.
entity_create('field_entity', array(
'field_name' => 'required_radio_test',
'name' => 'required_radio_test',
'entity_type' => 'entity_test',
'type' => 'list_text',
'settings' => array(
'allowed_values' => array('yes' => 'yes', 'no' => 'no'),
@ -378,7 +393,7 @@ class FormTest extends FieldTestBase {
function testFieldFormJSAddMore() {
$field = $this->field_unlimited;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
entity_create('field_entity', $field)->save();
entity_create('field_instance', $this->instance)->save();
@ -440,7 +455,7 @@ class FormTest extends FieldTestBase {
// Create a field with fixed cardinality and an instance using a multiple
// widget.
$field = $this->field_multiple;
$field_name = $field['field_name'];
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
entity_create('field_entity', $field)->save();
entity_create('field_instance', $this->instance)->save();
@ -485,10 +500,11 @@ class FormTest extends FieldTestBase {
* Tests fields with no 'edit' access.
*/
function testFieldFormAccess() {
$entity_type = 'entity_test_rev';
// Create a "regular" field.
$field = $this->field_single;
$field_name = $field['field_name'];
$entity_type = 'entity_test_rev';
$field['entity_type'] = $entity_type;
$field_name = $field['name'];
$instance = $this->instance;
$instance['field_name'] = $field_name;
$instance['entity_type'] = $entity_type;
@ -501,10 +517,11 @@ class FormTest extends FieldTestBase {
// Create a field with no edit access - see field_test_field_access().
$field_no_access = array(
'field_name' => 'field_no_edit_access',
'name' => 'field_no_edit_access',
'entity_type' => $entity_type,
'type' => 'test_field',
);
$field_name_no_access = $field_no_access['field_name'];
$field_name_no_access = $field_no_access['name'];
$instance_no_access = array(
'field_name' => $field_name_no_access,
'entity_type' => $entity_type,
@ -577,7 +594,8 @@ class FormTest extends FieldTestBase {
function testFieldFormHiddenWidget() {
$entity_type = 'entity_test_rev';
$field = $this->field_single;
$field_name = $field['field_name'];
$field['entity_type'] = $entity_type;
$field_name = $field['name'];
$this->instance['field_name'] = $field_name;
$this->instance['default_value'] = array(0 => array('value' => 99));
$this->instance['entity_type'] = $entity_type;

View File

@ -33,8 +33,17 @@ class NestedFormTest extends FieldTestBase {
$web_user = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
$this->drupalLogin($web_user);
$this->field_single = array('field_name' => 'field_single', 'type' => 'test_field');
$this->field_unlimited = array('field_name' => 'field_unlimited', 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED);
$this->field_single = array(
'name' => 'field_single',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$this->field_unlimited = array(
'name' => 'field_unlimited',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
);
$this->instance = array(
'entity_type' => 'entity_test',

View File

@ -42,7 +42,8 @@ class ShapeItemTest extends FieldUnitTestBase {
// Create an field field and instance for validation.
$field = array(
'field_name' => $this->field_name,
'name' => $this->field_name,
'entity_type' => 'entity_test',
'type' => 'shape',
);
entity_create('field_entity', $field)->save();

View File

@ -42,7 +42,8 @@ class TestItemTest extends FieldUnitTestBase {
// Create an field field and instance for validation.
$field = array(
'field_name' => $this->field_name,
'name' => $this->field_name,
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_entity', $field)->save();

View File

@ -85,13 +85,14 @@ class TranslationTest extends FieldUnitTestBase {
$this->entity_type = 'entity_test';
$this->field_definition = array(
'field_name' => $this->field_name,
'name' => $this->field_name,
'entity_type' => $this->entity_type,
'type' => 'test_field',
'cardinality' => 4,
'translatable' => TRUE,
);
entity_create('field_entity', $this->field_definition)->save();
$this->field = field_read_field($this->field_name);
$this->field = field_read_field($this->entity_type, $this->field_name);
$this->instance_definition = array(
'field_name' => $this->field_name,
@ -153,8 +154,7 @@ class TranslationTest extends FieldUnitTestBase {
// Prepare the field translations.
$entity_type = 'entity_test';
field_test_entity_info_translatable($entity_type, TRUE);
$id = $revision_id = 1;
$entity = entity_create($entity_type, array('id' => $id, 'revision_id' => $revision_id, 'type' => $this->instance['bundle']));
$entity = entity_create($entity_type, array('type' => $this->instance['bundle']));
$field_translations = array();
$available_langcodes = field_available_languages($entity_type, $this->field);
$this->assertTrue(count($available_langcodes) > 1, 'Field is translatable.');
@ -165,10 +165,7 @@ class TranslationTest extends FieldUnitTestBase {
}
// Save and reload the field translations.
field_attach_insert($entity);
$entity = entity_create($entity_type, array('id' => $id, 'revision_id' => $revision_id, 'type' => $this->instance['bundle']));
$entity->langcode->value = reset($available_langcodes);
field_attach_load($entity_type, array($id => $entity));
$entity = $this->entitySaveReload($entity);
// Check if the correct values were saved/loaded.
foreach ($field_translations as $langcode => $items) {
@ -182,7 +179,7 @@ class TranslationTest extends FieldUnitTestBase {
// Test default values.
$field_name_default = drupal_strtolower($this->randomName() . '_field_name');
$field_definition = $this->field_definition;
$field_definition['field_name'] = $field_name_default;
$field_definition['name'] = $field_name_default;
entity_create('field_entity', $field_definition)->save();
$instance_definition = $this->instance_definition;
@ -197,9 +194,7 @@ class TranslationTest extends FieldUnitTestBase {
asort($translation_langcodes);
$translation_langcodes = array_values($translation_langcodes);
$id++;
$revision_id++;
$values = array('id' => $id, 'revision_id' => $revision_id, 'type' => $instance['bundle'], 'langcode' => $translation_langcodes[0]);
$values = array('type' => $instance['bundle'], 'langcode' => $translation_langcodes[0]);
$entity = entity_create($entity_type, $values);
foreach ($translation_langcodes as $langcode) {
$values[$this->field_name][$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
@ -217,9 +212,7 @@ class TranslationTest extends FieldUnitTestBase {
// Check that explicit empty values are not overridden with default values.
foreach (array(NULL, array()) as $empty_items) {
$id++;
$revision_id++;
$values = array('id' => $id, 'revision_id' => $revision_id, 'type' => $instance['bundle'], 'langcode' => $translation_langcodes[0]);
$values = array('type' => $instance['bundle'], 'langcode' => $translation_langcodes[0]);
$entity = entity_create($entity_type, $values);
foreach ($translation_langcodes as $langcode) {
$values[$this->field_name][$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
@ -244,7 +237,8 @@ class TranslationTest extends FieldUnitTestBase {
// We need an additional field here to properly test display language
// suggestions.
$field = array(
'field_name' => $field_name,
'name' => $field_name,
'entity_type' => $entity_type,
'type' => 'test_field',
'cardinality' => 2,
'translatable' => TRUE,
@ -252,7 +246,7 @@ class TranslationTest extends FieldUnitTestBase {
entity_create('field_entity', $field)->save();
$instance = array(
'field_name' => $field['field_name'],
'field_name' => $field['name'],
'entity_type' => $entity_type,
'bundle' => 'entity_test',
);
@ -272,7 +266,7 @@ class TranslationTest extends FieldUnitTestBase {
// enabled.
foreach ($instances as $instance) {
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
$field = $instance->getField();
do {
// Index 0 is reserved for the requested language, this way we ensure
// that no field is actually populated with it.

View File

@ -33,7 +33,7 @@ class TranslationWebTest extends FieldTestBase {
*
* @var string
*/
protected $entity_type = 'test_entity';
protected $entity_type = 'entity_test_rev';
/**
* The field to use in this test.
@ -62,16 +62,15 @@ class TranslationWebTest extends FieldTestBase {
$this->field_name = drupal_strtolower($this->randomName() . '_field_name');
$this->entity_type = 'entity_test_rev';
$field = array(
'field_name' => $this->field_name,
'name' => $this->field_name,
'entity_type' => $this->entity_type,
'type' => 'test_field',
'cardinality' => 4,
'translatable' => TRUE,
);
entity_create('field_entity', $field)->save();
$this->field = field_read_field($this->field_name);
$this->field = field_read_field($this->entity_type, $this->field_name);
$instance = array(
'field_name' => $this->field_name,

View File

@ -6,6 +6,7 @@
*/
namespace Drupal\field\Tests\Views;
use Drupal\Core\Entity\DatabaseStorageController;
/**
* Test the produced views_data.
@ -28,9 +29,9 @@ class ApiDataTest extends FieldTestBase {
function setUp() {
parent::setUp();
$field_names = $this->setUpFields();
$field_names = $this->setUpFields(1);
// The first one will be attached to nodes only.
// Attach the field to nodes only.
$instance = array(
'field_name' => $field_names[0],
'entity_type' => 'node',
@ -38,33 +39,10 @@ class ApiDataTest extends FieldTestBase {
);
entity_create('field_instance', $instance)->save();
// The second one will be attached to users only.
$instance = array(
'field_name' => $field_names[1],
'entity_type' => 'user',
'bundle' => 'user',
);
entity_create('field_instance', $instance)->save();
// The third will be attached to both nodes and users.
$instance = array(
'field_name' => $field_names[2],
'entity_type' => 'node',
'bundle' => 'page',
);
entity_create('field_instance', $instance)->save();
$instance = array(
'field_name' => $field_names[2],
'entity_type' => 'user',
'bundle' => 'user',
);
entity_create('field_instance', $instance)->save();
// Now create some example nodes/users for the view result.
for ($i = 0; $i < 5; $i++) {
$edit = array(
'field_name_0' => array((array('value' => $this->randomName()))),
'field_name_2' => array((array('value' => $this->randomName()))),
$field_names[0] => array((array('value' => $this->randomName()))),
);
$nodes[] = $this->drupalCreateNode($edit);
}
@ -84,8 +62,8 @@ class ApiDataTest extends FieldTestBase {
// Check the table and the joins of the first field.
// Attached to node only.
$field = $this->fields[0];
$current_table = _field_sql_storage_tablename($field);
$revision_table = _field_sql_storage_revision_tablename($field);
$current_table = DatabaseStorageController::_fieldTableName($field);
$revision_table = DatabaseStorageController::_fieldRevisionTableName($field);
$data[$current_table] = $views_data->get($current_table);
$data[$revision_table] = $views_data->get($revision_table);
@ -99,7 +77,6 @@ class ApiDataTest extends FieldTestBase {
'left_field' => 'nid',
'field' => 'entity_id',
'extra' => array(
array('field' => 'entity_type', 'value' => 'node'),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
),
);
@ -108,54 +85,10 @@ class ApiDataTest extends FieldTestBase {
'left_field' => 'vid',
'field' => 'revision_id',
'extra' => array(
array('field' => 'entity_type', 'value' => 'node'),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
),
);
$this->assertEqual($expected_join, $data[$revision_table]['table']['join']['node_field_revision']);
// Check the table and the joins of the second field.
// Attached to both node and user.
$field_2 = $this->fields[2];
$current_table_2 = _field_sql_storage_tablename($field_2);
$revision_table_2 = _field_sql_storage_revision_tablename($field_2);
$data[$current_table_2] = $views_data->get($current_table_2);
$data[$revision_table_2] = $views_data->get($revision_table_2);
$this->assertTrue(isset($data[$current_table_2]));
$this->assertTrue(isset($data[$revision_table_2]));
// The second field should join against both node and users.
$this->assertTrue(isset($data[$current_table_2]['table']['join']['node']));
$this->assertTrue(isset($data[$revision_table_2]['table']['join']['node_field_revision']));
$this->assertTrue(isset($data[$current_table_2]['table']['join']['users']));
$expected_join = array(
'left_field' => 'nid',
'field' => 'entity_id',
'extra' => array(
array('field' => 'entity_type', 'value' => 'node'),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
)
);
$this->assertEqual($expected_join, $data[$current_table_2]['table']['join']['node']);
$expected_join = array(
'left_field' => 'vid',
'field' => 'revision_id',
'extra' => array(
array('field' => 'entity_type', 'value' => 'node'),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
)
);
$this->assertEqual($expected_join, $data[$revision_table_2]['table']['join']['node_field_revision']);
$expected_join = array(
'left_field' => 'uid',
'field' => 'entity_id',
'extra' => array(
array('field' => 'entity_type', 'value' => 'user'),
array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
)
);
$this->assertEqual($expected_join, $data[$current_table_2]['table']['join']['users']);
}
}

View File

@ -59,7 +59,11 @@ abstract class FieldTestBase extends ViewTestBase {
$field_names = array();
for ($i = 0; $i < $amount; $i++) {
$field_names[$i] = 'field_name_' . $i;
$field = array('field_name' => $field_names[$i], 'type' => 'text');
$field = array(
'name' => $field_names[$i],
'entity_type' => 'node',
'type' => 'text',
);
$this->fields[$i] = $field = entity_create('field_entity', $field);
$field->save();
@ -70,7 +74,7 @@ abstract class FieldTestBase extends ViewTestBase {
function setUpInstances($bundle = 'page') {
foreach ($this->fields as $key => $field) {
$instance = array(
'field_name' => $field['field_name'],
'field_name' => $field['name'],
'entity_type' => 'node',
'bundle' => 'page',
);

View File

@ -47,10 +47,20 @@ class HandlerFieldFieldTest extends FieldTestBase {
$this->setUpFields(3);
// Setup a field with cardinality > 1.
$this->fields[3] = $field = entity_create('field_entity', array('field_name' => 'field_name_3', 'type' => 'text', 'cardinality' => FIELD_CARDINALITY_UNLIMITED));
$this->fields[3] = $field = entity_create('field_entity', array(
'name' => 'field_name_3',
'entity_type' => 'node',
'type' => 'text',
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
));
$field->save();
// Setup a field that will have no value.
$this->fields[4] = $field = entity_create('field_entity', array('field_name' => 'field_name_4', 'type' => 'text', 'cardinality' => FIELD_CARDINALITY_UNLIMITED));
$this->fields[4] = $field = entity_create('field_entity', array(
'name' => 'field_name_4',
'entity_type' => 'node',
'type' => 'text',
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
));
$field->save();
$this->setUpInstances();
@ -86,7 +96,7 @@ class HandlerFieldFieldTest extends FieldTestBase {
$view->initDisplay();
foreach ($this->fields as $key => $field) {
$view->display_handler->options['fields'][$field['field_name']]['id'] = $field['field_name'];
$view->display_handler->options['fields'][$field['field_name']]['table'] = 'field_data_' . $field['field_name'];
$view->display_handler->options['fields'][$field['field_name']]['table'] = 'node__' . $field['field_name'];
$view->display_handler->options['fields'][$field['field_name']]['field'] = $field['field_name'];
}
}

View File

@ -21,7 +21,6 @@ class reEnableModuleFieldTest extends WebTestBase {
*/
public static $modules = array(
'field',
'field_sql_storage',
'node',
// We use telephone module instead of test_field because test_field is
// hidden and does not display on the admin/modules page.
@ -51,7 +50,8 @@ class reEnableModuleFieldTest extends WebTestBase {
// Add a telephone field to the article content type.
$field = entity_create('field_entity', array(
'field_name' => 'field_telephone',
'name' => 'field_telephone',
'entity_type' => 'node',
'type' => 'telephone',
));
$field->save();

View File

@ -18,7 +18,6 @@ use Drupal\field\FieldInterface;
require_once __DIR__ . '/field_test.entity.inc';
require_once __DIR__ . '/field_test.field.inc';
require_once __DIR__ . '/field_test.storage.inc';
/**
* Implements hook_permission().

View File

@ -195,7 +195,7 @@ function field_test_field_storage_delete(EntityInterface $entity, $fields) {
// does, is highly inefficient in our case...
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
if (isset($fields[$instance['field_id']])) {
$field = field_info_field_by_id($instance['field_id']);
$field = $instance->getField();
field_test_field_storage_purge($entity, $field, $instance);
}
}
@ -403,7 +403,7 @@ function field_test_field_storage_delete_field($field) {
function field_test_field_storage_delete_instance($instance) {
$data = _field_test_storage_data();
$field = field_info_field($instance['field_name']);
$field = $instance->getField();
$field_data = &$data[$field['uuid']];
foreach (array('current', 'revisions') as $sub_table) {
foreach ($field_data[$sub_table] as &$row) {
@ -424,8 +424,8 @@ function field_test_entity_bundle_rename($entity_type, $bundle_old, $bundle_new)
// We need to account for deleted or inactive fields and instances.
$instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
foreach ($instances as $field_name => $instance) {
$field = field_info_field_by_id($instance['field_id']);
foreach ($instances as $instance) {
$field = $instance->getField();
if ($field && $field['storage']['type'] == 'field_test_storage') {
$field_data = &$data[$field['uuid']];
foreach (array('current', 'revisions') as $sub_table) {

View File

@ -1,16 +1,12 @@
id: field_test_import
id: entity_test.field_test_import
langcode: und
name: field_test_import
entity_type: entity_test
type: text
settings:
max_length: '255'
module: text
active: '1'
entity_types: { }
storage:
type: field_sql_storage
settings: { }
module: field_sql_storage
active: '1'
locked: '0'
cardinality: '1'
translatable: false

Some files were not shown because too many files have changed in this diff Show More