summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorDavid Rothstein <drothstein@gmail.com>2013-01-27 16:21:18 -0500
committerDavid Rothstein <drothstein@gmail.com>2013-01-27 16:21:18 -0500
commitbd1dc1fdbb428bc745f26b1183e1052603cd1d6e (patch)
treea695ab119da16a609e7b7637bd882302a0230f30 /modules
parent6a4df4adedc949f7441493e75e8ae73f847bfa10 (diff)
downloadbrdo-bd1dc1fdbb428bc745f26b1183e1052603cd1d6e.tar.gz
brdo-bd1dc1fdbb428bc745f26b1183e1052603cd1d6e.tar.bz2
Issue #1040790 by yched, swentel, geerlingguy, justin.randell, Berdir | catch: Fixed _field_info_collate_fields() memory usage.
Diffstat (limited to 'modules')
-rw-r--r--modules/field/field.api.php7
-rw-r--r--modules/field/field.attach.inc6
-rw-r--r--modules/field/field.crud.inc19
-rw-r--r--modules/field/field.info1
-rw-r--r--modules/field/field.info.class.inc668
-rw-r--r--modules/field/field.info.inc449
-rw-r--r--modules/field/field.install7
-rw-r--r--modules/field/field.module3
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module7
-rw-r--r--modules/field/tests/field.test144
-rw-r--r--modules/field/tests/field_test.entity.inc6
-rw-r--r--modules/field_ui/field_ui.module23
-rw-r--r--modules/field_ui/field_ui.test6
13 files changed, 1051 insertions, 295 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 90a09fef8..822f537bc 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1735,11 +1735,14 @@ function hook_field_storage_details_alter(&$details, $field) {
* loaded.
*/
function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) {
- $field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
- $field = $field_info[$field_id];
+ // 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);
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index 5bebe9b35..dec5c20aa 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -283,7 +283,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
'language' => NULL,
);
$options += $default_options;
- $field_info = field_info_field_by_ids();
$fields = array();
$grouped_instances = array();
@@ -307,7 +306,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
foreach ($instances as $instance) {
$field_id = $instance['field_id'];
$field_name = $instance['field_name'];
- $field = $field_info[$field_id];
+ $field = field_info_field_by_id($field_id);
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
if (function_exists($function)) {
// Add the field to the list of fields to invoke the hook on.
@@ -614,7 +613,6 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod
* non-deleted fields are operated on.
*/
function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
- $field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
// Merge default options.
@@ -692,7 +690,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
}
// Collect the storage backend if the field has not been loaded yet.
if (!isset($skip_fields[$field_id])) {
- $field = $field_info[$field_id];
+ $field = field_info_field_by_id($field_id);
$storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
}
}
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index f9c96c92b..e1acdd53b 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -319,7 +319,11 @@ function field_read_field($field_name, $include_additional = array()) {
* Reads in fields that match an array of conditions.
*
* @param array $params
- * An array of conditions to match against.
+ * An array of conditions to match against. Keys are columns from the
+ * 'field_config' table, values are conditions to match. Additionally,
+ * conditions on the 'entity_type' and 'bundle' columns from the
+ * 'field_config_instance' table are supported (select fields having an
+ * instance on a given bundle).
* @param array $include_additional
* The default behavior of this function is to not return fields that
* are inactive or have been deleted. Setting
@@ -337,8 +341,21 @@ function field_read_fields($params = array(), $include_additional = array()) {
// Turn the conditions into a query.
foreach ($params as $key => $value) {
+ // Allow filtering on the 'entity_type' and 'bundle' columns of the
+ // field_config_instance table.
+ if ($key == 'entity_type' || $key == 'bundle') {
+ if (empty($fci_join)) {
+ $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id');
+ }
+ $key = 'fci.' . $key;
+ }
+ else {
+ $key = 'fc.' . $key;
+ }
+
$query->condition($key, $value);
}
+
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
$query
->condition('fc.active', 1)
diff --git a/modules/field/field.info b/modules/field/field.info
index f8a331a00..fcdca88e6 100644
--- a/modules/field/field.info
+++ b/modules/field/field.info
@@ -5,6 +5,7 @@ version = VERSION
core = 7.x
files[] = field.module
files[] = field.attach.inc
+files[] = field.info.class.inc
files[] = tests/field.test
dependencies[] = field_sql_storage
required = TRUE
diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc
new file mode 100644
index 000000000..de50a1bf0
--- /dev/null
+++ b/modules/field/field.info.class.inc
@@ -0,0 +1,668 @@
+<?php
+
+/*
+ * @file
+ * Definition of the FieldInfo class.
+ */
+
+/**
+ * Provides field and instance definitions for the current runtime environment.
+ *
+ * A FieldInfo object is created and statically persisted through the request
+ * by the _field_info_field_cache() function. The object properties act as a
+ * "static cache" of fields and instances definitions.
+ *
+ * The preferred way to access definitions is through the getBundleInstances()
+ * method, which keeps cache entries per bundle, storing both fields and
+ * instances for a given bundle. Fields used in multiple bundles are duplicated
+ * in several cache entries, and are merged into a single list in the memory
+ * cache. Cache entries are loaded for bundles as a whole, optimizing memory
+ * and CPU usage for the most common pattern of iterating over all instances of
+ * a bundle rather than accessing a single instance.
+ *
+ * The getFields() and getInstances() methods, which return all existing field
+ * and instance definitions, are kept mainly for backwards compatibility, and
+ * should be avoided when possible, since they load and persist in memory a
+ * potentially large array of information. In many cases, the lightweight
+ * getFieldMap() method should be preferred.
+ */
+class FieldInfo {
+
+ /**
+ * Lightweight map of fields across entity types and bundles.
+ *
+ * @var array
+ */
+ protected $fieldMap;
+
+ /**
+ * List of $field structures keyed by ID. Includes deleted fields.
+ *
+ * @var array
+ */
+ protected $fieldsById = array();
+
+ /**
+ * Mapping of field names to the ID of the corresponding non-deleted field.
+ *
+ * @var array
+ */
+ protected $fieldIdsByName = array();
+
+ /**
+ * Whether $fieldsById contains all field definitions or a subset.
+ *
+ * @var bool
+ */
+ protected $loadedAllFields = FALSE;
+
+ /**
+ * Separately tracks requested field names or IDs that do not exist.
+ *
+ * @var array
+ */
+ protected $unknownFields = array();
+
+ /**
+ * Instance definitions by bundle.
+ *
+ * @var array
+ */
+ protected $bundleInstances = array();
+
+ /**
+ * Whether $bundleInstances contains all instances definitions or a subset.
+ *
+ * @var bool
+ */
+ protected $loadedAllInstances = FALSE;
+
+ /**
+ * Separately tracks requested bundles that are empty (or do not exist).
+ *
+ * @var array
+ */
+ protected $emptyBundles = array();
+
+ /**
+ * Extra fields by bundle.
+ *
+ * @var array
+ */
+ protected $bundleExtraFields = array();
+
+ /**
+ * Clears the "static" and persistent caches.
+ */
+ public function flush() {
+ $this->fieldMap = NULL;
+
+ $this->fieldsById = array();
+ $this->fieldIdsByName = array();
+ $this->loadedAllFields = FALSE;
+ $this->unknownFields = array();
+
+ $this->bundleInstances = array();
+ $this->loadedAllInstances = FALSE;
+ $this->emptyBundles = array();
+
+ $this->bundleExtraFields = array();
+
+ cache_clear_all('field_info:', 'cache_field', TRUE);
+ }
+
+ /**
+ * Collects a lightweight map of fields across bundles.
+ *
+ * @return
+ * An array keyed by field name. Each 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.
+ */
+ public function getFieldMap() {
+ // Read from the "static" cache.
+ if ($this->fieldMap !== NULL) {
+ return $this->fieldMap;
+ }
+
+ // Read from persistent cache.
+ if ($cached = cache_get('field_info:field_map', 'cache_field')) {
+ $map = $cached->data;
+
+ // Save in "static" cache.
+ $this->fieldMap = $map;
+
+ return $map;
+ }
+
+ $map = array();
+
+ $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0');
+ foreach ($query as $row) {
+ $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle;
+ $map[$row->field_name]['type'] = $row->type;
+ }
+
+ // Save in "static" and persistent caches.
+ $this->fieldMap = $map;
+ cache_set('field_info:field_map', $map, 'cache_field');
+
+ return $map;
+ }
+
+ /**
+ * Returns all active fields, including deleted ones.
+ *
+ * @return
+ * An array of field definitions, keyed by field ID.
+ */
+ public function getFields() {
+ // Read from the "static" cache.
+ if ($this->loadedAllFields) {
+ return $this->fieldsById;
+ }
+
+ // Read from persistent cache.
+ if ($cached = cache_get('field_info:fields', 'cache_field')) {
+ $this->fieldsById = $cached->data;
+ }
+ else {
+ // Collect and prepare fields.
+ foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) {
+ $this->fieldsById[$field['id']] = $this->prepareField($field);
+ }
+
+ // Store in persistent cache.
+ cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+ }
+
+ // Fill the name/ID map.
+ foreach ($this->fieldsById as $field) {
+ if (!$field['deleted']) {
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+ }
+
+ $this->loadedAllFields = TRUE;
+
+ return $this->fieldsById;
+ }
+
+ /**
+ * Retrieves all active, non-deleted instances definitions.
+ *
+ * @param $entity_type
+ * (optional) The entity type.
+ *
+ * @return
+ * If $entity_type is not set, all instances keyed by entity type and bundle
+ * name. If $entity_type is set, all instances for that entity type, keyed
+ * by bundle name.
+ */
+ public function getInstances($entity_type = NULL) {
+ // If the full list is not present in "static" cache yet.
+ if (!$this->loadedAllInstances) {
+
+ // Read from persistent cache.
+ if ($cached = cache_get('field_info:instances', 'cache_field')) {
+ $this->bundleInstances = $cached->data;
+ }
+ else {
+ // Collect and prepare instances.
+
+ // We also need to populate the static field cache, since it will not
+ // be set by subsequent getBundleInstances() calls.
+ $this->getFields();
+
+ // Initialize empty arrays for all existing entity types and bundles.
+ // This is not strictly needed, but is done to preserve the behavior of
+ // field_info_instances() before http://drupal.org/node/1880666.
+ foreach (field_info_bundles() as $existing_entity_type => $bundles) {
+ foreach ($bundles as $bundle => $bundle_info) {
+ $this->bundleInstances[$existing_entity_type][$bundle] = array();
+ }
+ }
+
+ foreach (field_read_instances() as $instance) {
+ $field = $this->getField($instance['field_name']);
+ $instance = $this->prepareInstance($instance, $field['type']);
+ $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
+ }
+
+ // Store in persistent cache.
+ cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+ }
+
+ $this->loadedAllInstances = TRUE;
+ }
+
+ if (isset($entity_type)) {
+ return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array();
+ }
+ else {
+ return $this->bundleInstances;
+ }
+ }
+
+ /**
+ * Returns a field definition from a field name.
+ *
+ * This method only retrieves active, non-deleted fields.
+ *
+ * @param $field_name
+ * The field name.
+ *
+ * @return
+ * The field definition, or NULL if no field was found.
+ */
+ public function getField($field_name) {
+ // Read from the "static" cache.
+ if (isset($this->fieldIdsByName[$field_name])) {
+ $field_id = $this->fieldIdsByName[$field_name];
+ return $this->fieldsById[$field_id];
+ }
+ if (isset($this->unknownFields[$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)) {
+ $field = $this->prepareField($field);
+
+ // Save in the "static" cache.
+ $this->fieldsById[$field['id']] = $field;
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+
+ return $field;
+ }
+ else {
+ $this->unknownFields[$field_name] = TRUE;
+ }
+ }
+
+ /**
+ * Returns a field definition from a field ID.
+ *
+ * This method only retrieves active fields, deleted or not.
+ *
+ * @param $field_id
+ * The field ID.
+ *
+ * @return
+ * The field definition, or NULL if no field was found.
+ */
+ public function getFieldById($field_id) {
+ // Read from the "static" cache.
+ if (isset($this->fieldsById[$field_id])) {
+ return $this->fieldsById[$field_id];
+ }
+ if (isset($this->unknownFields[$field_id])) {
+ return;
+ }
+
+ // No persistent cache, fields are only persistently cached as part of a
+ // bundle.
+
+ // Cache miss: read from definition.
+ if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
+ $field = current($fields);
+ $field = $this->prepareField($field);
+
+ // Store in the static cache.
+ $this->fieldsById[$field['id']] = $field;
+ if (!$field['deleted']) {
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+
+ return $field;
+ }
+ else {
+ $this->unknownFields[$field_id] = TRUE;
+ }
+ }
+
+ /**
+ * Retrieves the instances for a bundle.
+ *
+ * The function also populates the corresponding field definitions in the
+ * "static" cache.
+ *
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle
+ * The bundle name.
+ *
+ * @return
+ * The array of instance definitions, keyed by field name.
+ */
+ public function getBundleInstances($entity_type, $bundle) {
+ // Read from the "static" cache.
+ if (isset($this->bundleInstances[$entity_type][$bundle])) {
+ return $this->bundleInstances[$entity_type][$bundle];
+ }
+ if (isset($this->emptyBundles[$entity_type][$bundle])) {
+ return array();
+ }
+
+ // Read from the persistent cache.
+ if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) {
+ $info = $cached->data;
+
+ // Extract the field definitions and save them in the "static" cache.
+ foreach ($info['fields'] as $field) {
+ if (!isset($this->fieldsById[$field['id']])) {
+ $this->fieldsById[$field['id']] = $field;
+ if (!$field['deleted']) {
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+ }
+ }
+ unset($info['fields']);
+
+ // Store the instance definitions in the "static" cache'. Empty (or
+ // non-existent) bundles are stored separately, so that they do not
+ // pollute the global list returned by getInstances().
+ if ($info['instances']) {
+ $this->bundleInstances[$entity_type][$bundle] = $info['instances'];
+ }
+ else {
+ $this->emptyBundles[$entity_type][$bundle] = TRUE;
+ }
+
+ return $info['instances'];
+ }
+
+ // Cache miss: collect from the definitions.
+
+ $instances = array();
+
+ // Collect the fields in the bundle.
+ $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
+ $fields = field_read_fields($params);
+
+ // This iterates on non-deleted instances, so deleted fields are kept out of
+ // the persistent caches.
+ foreach (field_read_instances($params) as $instance) {
+ $field = $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.
+ if (!isset($this->fieldsById[$field['id']])) {
+ $field = $this->prepareField($field);
+
+ $this->fieldsById[$field['id']] = $field;
+ $this->fieldIdsByName[$field['field_name']] = $field['id'];
+ }
+ }
+
+ // Store in the 'static' cache'. Empty (or non-existent) bundles are stored
+ // separately, so that they do not pollute the global list returned by
+ // getInstances().
+ if ($instances) {
+ $this->bundleInstances[$entity_type][$bundle] = $instances;
+ }
+ else {
+ $this->emptyBundles[$entity_type][$bundle] = TRUE;
+ }
+
+ // The persistent cache additionally contains the definitions of the fields
+ // involved in the bundle.
+ $cache = array(
+ 'instances' => $instances,
+ 'fields' => array()
+ );
+ foreach ($instances as $instance) {
+ $cache['fields'][] = $this->fieldsById[$instance['field_id']];
+ }
+ cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+
+ return $instances;
+ }
+
+ /**
+ * Retrieves the "extra fields" for a bundle.
+ *
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle
+ * The bundle name.
+ *
+ * @return
+ * The array of extra fields.
+ */
+ public function getBundleExtraFields($entity_type, $bundle) {
+ // Read from the "static" cache.
+ if (isset($this->bundleExtraFields[$entity_type][$bundle])) {
+ return $this->bundleExtraFields[$entity_type][$bundle];
+ }
+
+ // Read from the persistent cache.
+ if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) {
+ $this->bundleExtraFields[$entity_type][$bundle] = $cached->data;
+ return $this->bundleExtraFields[$entity_type][$bundle];
+ }
+
+ // Cache miss: read from hook_field_extra_fields(). Note: given the current
+ // shape of the hook, we have no other way than collecting extra fields on
+ // all bundles.
+ $info = array();
+ $extra = module_invoke_all('field_extra_fields');
+ drupal_alter('field_extra_fields', $extra);
+ // Merge in saved settings.
+ if (isset($extra[$entity_type][$bundle])) {
+ $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle);
+ }
+
+ // Store in the 'static' and persistent caches.
+ $this->bundleExtraFields[$entity_type][$bundle] = $info;
+ cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+
+ return $this->bundleExtraFields[$entity_type][$bundle];
+ }
+
+ /**
+ * Prepares a field definition for the current run-time context.
+ *
+ * @param $field
+ * The raw field structure as read from the database.
+ *
+ * @return
+ * The field definition completed for the current runtime context.
+ */
+ public function prepareField($field) {
+ // Make sure all expected field settings are present.
+ $field['settings'] += field_info_field_settings($field['type']);
+ $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
+
+ // Add storage details.
+ $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
+ drupal_alter('field_storage_details', $details, $field);
+ $field['storage']['details'] = $details;
+
+ // Populate the list of bundles using the field.
+ $field['bundles'] = array();
+ if (!$field['deleted']) {
+ $map = $this->getFieldMap();
+ if (isset($map[$field['field_name']])) {
+ $field['bundles'] = $map[$field['field_name']]['bundles'];
+ }
+ }
+
+ return $field;
+ }
+
+ /**
+ * Prepares an instance definition for the current run-time context.
+ *
+ * @param $instance
+ * The raw instance structure as read from the database.
+ * @param $field_type
+ * The field type.
+ *
+ * @return
+ * The field instance array completed for the current runtime context.
+ */
+ public function prepareInstance($instance, $field_type) {
+ // Make sure all expected instance settings are present.
+ $instance['settings'] += field_info_instance_settings($field_type);
+
+ // Set a default value for the instance.
+ if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
+ $instance['default_value'] = NULL;
+ }
+
+ // Prepare widget settings.
+ $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type);
+
+ // Prepare display settings.
+ foreach ($instance['display'] as $view_mode => $display) {
+ $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type);
+ }
+
+ // Fall back to 'hidden' for view modes configured to use custom display
+ // settings, and for which the instance has no explicit settings.
+ $entity_info = entity_get_info($instance['entity_type']);
+ $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
+ $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
+ foreach ($view_modes as $view_mode) {
+ if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
+ if (!isset($instance['display'][$view_mode])) {
+ $instance['display'][$view_mode] = array(
+ 'type' => 'hidden',
+ 'label' => 'above',
+ 'settings' => array(),
+ 'weight' => 0,
+ );
+ }
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Prepares widget properties for the current run-time context.
+ *
+ * @param $widget
+ * Widget specifications as found in $instance['widget'].
+ * @param $field_type
+ * The field type.
+ *
+ * @return
+ * The widget properties completed for the current runtime context.
+ */
+ public function prepareInstanceWidget($widget, $field_type) {
+ $field_type_info = field_info_field_types($field_type);
+
+ // Fill in default values.
+ $widget += array(
+ 'type' => $field_type_info['default_widget'],
+ 'settings' => array(),
+ 'weight' => 0,
+ );
+
+ $widget_type_info = field_info_widget_types($widget['type']);
+ // Fall back to default formatter if formatter type is not available.
+ if (!$widget_type_info) {
+ $widget['type'] = $field_type_info['default_widget'];
+ $widget_type_info = field_info_widget_types($widget['type']);
+ }
+ $widget['module'] = $widget_type_info['module'];
+ // Fill in default settings for the widget.
+ $widget['settings'] += field_info_widget_settings($widget['type']);
+
+ return $widget;
+ }
+
+ /**
+ * Adapts display specifications to the current run-time context.
+ *
+ * @param $display
+ * Display specifications as found in $instance['display']['a_view_mode'].
+ * @param $field_type
+ * The field type.
+ *
+ * @return
+ * The display properties completed for the current runtime context.
+ */
+ public function prepareInstanceDisplay($display, $field_type) {
+ $field_type_info = field_info_field_types($field_type);
+
+ // Fill in default values.
+ $display += array(
+ 'label' => 'above',
+ 'type' => $field_type_info['default_formatter'],
+ 'settings' => array(),
+ 'weight' => 0,
+ );
+ if ($display['type'] != 'hidden') {
+ $formatter_type_info = field_info_formatter_types($display['type']);
+ // Fall back to default formatter if formatter type is not available.
+ if (!$formatter_type_info) {
+ $display['type'] = $field_type_info['default_formatter'];
+ $formatter_type_info = field_info_formatter_types($display['type']);
+ }
+ $display['module'] = $formatter_type_info['module'];
+ // Fill in default settings for the formatter.
+ $display['settings'] += field_info_formatter_settings($display['type']);
+ }
+
+ return $display;
+ }
+
+ /**
+ * Prepares 'extra fields' for the current run-time context.
+ *
+ * @param $extra_fields
+ * The array of extra fields, as collected in hook_field_extra_fields().
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle
+ * The bundle name.
+ *
+ * @return
+ * The list of extra fields completed for the current runtime context.
+ */
+ public function prepareExtraFields($extra_fields, $entity_type, $bundle) {
+ $entity_type_info = entity_get_info($entity_type);
+ $bundle_settings = field_bundle_settings($entity_type, $bundle);
+ $extra_fields += array('form' => array(), 'display' => array());
+
+ $result = array();
+ // Extra fields in forms.
+ foreach ($extra_fields['form'] as $name => $field_data) {
+ $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
+ if (isset($settings['weight'])) {
+ $field_data['weight'] = $settings['weight'];
+ }
+ $result['form'][$name] = $field_data;
+ }
+
+ // Extra fields in displayed entities.
+ $data = $extra_fields['display'];
+ foreach ($extra_fields['display'] as $name => $field_data) {
+ $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
+ $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
+ foreach ($view_modes as $view_mode) {
+ if (isset($settings[$view_mode])) {
+ $field_data['display'][$view_mode] = $settings[$view_mode];
+ }
+ else {
+ $field_data['display'][$view_mode] = array(
+ 'weight' => $field_data['weight'],
+ 'visible' => TRUE,
+ );
+ }
+ }
+ unset($field_data['weight']);
+ $result['display'][$name] = $field_data;
+ }
+
+ return $result;
+ }
+}
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index 9e7ab938d..77113957d 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -6,6 +6,32 @@
*/
/**
+ * Retrieves the FieldInfo object for the current request.
+ *
+ * @return FieldInfo
+ * An instance of the FieldInfo class.
+ */
+function _field_info_field_cache() {
+ // 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_field_cache'] = &drupal_static(__FUNCTION__);
+ }
+ $field_info = &$drupal_static_fast['field_info_field_cache'];
+
+ if (!isset($field_info)) {
+ // @todo The registry should save the need for an explicit include, but not
+ // a couple upgrade tests (DisabledNodeTypeTestCase,
+ // FilterFormatUpgradePathTestCase...) break in a strange way without it.
+ include_once dirname(__FILE__) . '/field.info.class.inc';
+ $field_info = new FieldInfo();
+ }
+
+ return $field_info;
+}
+
+/**
* @defgroup field_info Field Info API
* @{
* Obtain information about Field API configuration.
@@ -34,7 +60,50 @@ function field_info_cache_clear() {
entity_info_cache_clear();
_field_info_collate_types(TRUE);
- _field_info_collate_fields(TRUE);
+ _field_info_field_cache()->flush();
+}
+
+/**
+ * Collates all information on existing fields and instances.
+ *
+ * Deprecated. This function is kept to ensure backwards compatibility, but has
+ * a serious performance impact, and should be absolutely avoided.
+ * See http://drupal.org/node/1880666.
+ *
+ * Use the regular field_info_*() API functions to access the information, or
+ * field_info_cache_clear() to clear the cached data.
+ */
+function _field_info_collate_fields($reset = FALSE) {
+ if ($reset) {
+ _field_info_field_cache()->flush();
+ return;
+ }
+
+ $cache = _field_info_field_cache();
+
+ // Collect fields, and build the array of IDs keyed by field_name.
+ $fields = $cache->getFields();
+ $field_ids = array();
+ foreach ($fields as $id => $field) {
+ if (!$field['deleted']) {
+ $field_ids[$field['field_name']] = $id;
+ }
+ }
+
+ // Collect extra fields for all entity types.
+ $extra_fields = array();
+ foreach (field_info_bundles() as $entity_type => $bundles) {
+ foreach ($bundles as $bundle => $info) {
+ $extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle);
+ }
+ }
+
+ return array(
+ 'fields' => $fields,
+ 'field_ids' => $field_ids,
+ 'instances' => $cache->getInstances(),
+ 'extra_fields' => $extra_fields,
+ );
}
/**
@@ -162,281 +231,68 @@ function _field_info_collate_types($reset = FALSE) {
}
/**
- * Collates all information on existing fields and instances.
- *
- * @param $reset
- * If TRUE, clear the cache. The information will be rebuilt from the
- * database next time it is needed. Defaults to FALSE.
- *
- * @return
- * If $reset is TRUE, nothing.
- * If $reset is FALSE, an array containing the following elements:
- * - fields: Array of existing fields, keyed by field ID. This element
- * lists deleted and non-deleted fields, but not inactive ones.
- * Each field has an additional element, 'bundles', which is an array
- * of all non-deleted instances of that field.
- * - field_ids: Array of field IDs, keyed by field name. This element
- * only lists non-deleted, active fields.
- * - instances: Array of existing instances, keyed by entity type, bundle
- * name and field name. This element only lists non-deleted instances
- * whose field is active.
- */
-function _field_info_collate_fields($reset = FALSE) {
- static $info;
-
- if ($reset) {
- $info = NULL;
- cache_clear_all('field_info_fields', 'cache_field');
- return;
- }
-
- if (!isset($info)) {
- if ($cached = cache_get('field_info_fields', 'cache_field')) {
- $info = $cached->data;
- }
- else {
- $definitions = array(
- 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)),
- 'instances' => field_read_instances(),
- );
-
- // Populate 'fields' with all fields, keyed by ID.
- $info['fields'] = array();
- foreach ($definitions['field_ids'] as $key => $field) {
- $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field);
- }
-
- // Build an array of field IDs for non-deleted fields, keyed by name.
- $info['field_ids'] = array();
- foreach ($info['fields'] as $key => $field) {
- if (!$field['deleted']) {
- $info['field_ids'][$field['field_name']] = $key;
- }
- }
-
- // Populate 'instances'. Only non-deleted instances are considered.
- $info['instances'] = array();
- foreach (field_info_bundles() as $entity_type => $bundles) {
- foreach ($bundles as $bundle => $bundle_info) {
- $info['instances'][$entity_type][$bundle] = array();
- }
- }
- foreach ($definitions['instances'] as $instance) {
- $field = $info['fields'][$instance['field_id']];
- $instance = _field_info_prepare_instance($instance, $field);
- $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
- // Enrich field definitions with the list of bundles where they have
- // instances. NOTE: Deleted fields in $info['field_ids'] are not
- // enriched because all of their instances are deleted, too, and
- // are thus not in $definitions['instances'].
- $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle'];
- }
-
- // Populate 'extra_fields'.
- $extra = module_invoke_all('field_extra_fields');
- drupal_alter('field_extra_fields', $extra);
- // Merge in saved settings.
- foreach ($extra as $entity_type => $bundles) {
- foreach ($bundles as $bundle => $extra_fields) {
- $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle);
- $info['extra_fields'][$entity_type][$bundle] = $extra_fields;
- }
- }
-
- cache_set('field_info_fields', $info, 'cache_field');
- }
- }
-
- return $info;
-}
-
-/**
* Prepares a field definition for the current run-time context.
*
- * Since the field was last saved or updated, new field settings can be
- * expected.
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1880666.
*
- * @param $field
- * The raw field structure as read from the database.
+ * @see FieldInfo::prepareField()
*/
function _field_info_prepare_field($field) {
- // Make sure all expected field settings are present.
- $field['settings'] += field_info_field_settings($field['type']);
- $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
-
- // Add storage details.
- $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
- drupal_alter('field_storage_details', $details, $field, $instance);
- $field['storage']['details'] = $details;
-
- // Initialize the 'bundles' list.
- $field['bundles'] = array();
-
- return $field;
+ $cache = _field_info_field_cache();
+ return $cache->prepareField($field);
}
/**
* Prepares an instance definition for the current run-time context.
*
- * Since the instance was last saved or updated, a number of things might have
- * changed: widgets or formatters disabled, new settings expected, new view
- * modes added...
- *
- * @param $instance
- * The raw instance structure as read from the database.
- * @param $field
- * The field structure for the instance.
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1880666.
*
- * @return
- * Field instance array.
+ * @see FieldInfo::prepareInstance()
*/
function _field_info_prepare_instance($instance, $field) {
- // Make sure all expected instance settings are present.
- $instance['settings'] += field_info_instance_settings($field['type']);
-
- // Set a default value for the instance.
- if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
- $instance['default_value'] = NULL;
- }
-
- $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']);
-
- foreach ($instance['display'] as $view_mode => $display) {
- $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display);
- }
-
- // Fallback to 'hidden' for view modes configured to use custom display
- // settings, and for which the instance has no explicit settings.
- $entity_info = entity_get_info($instance['entity_type']);
- $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
- $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
- foreach ($view_modes as $view_mode) {
- if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
- if (!isset($instance['display'][$view_mode])) {
- $instance['display'][$view_mode] = array(
- 'type' => 'hidden',
- 'label' => 'above',
- 'settings' => array(),
- 'weight' => 0,
- );
- }
- }
- }
-
- return $instance;
+ $cache = _field_info_field_cache();
+ return $cache->prepareInstance($instance, $field['type']);
}
/**
* Adapts display specifications to the current run-time context.
*
- * @param $field
- * The field structure for the instance.
- * @param $display
- * Display specifications as found in
- * $instance['display']['some_view_mode'].
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1880666.
+ *
+ * @see FieldInfo::prepareInstanceDisplay()
*/
function _field_info_prepare_instance_display($field, $display) {
- $field_type = field_info_field_types($field['type']);
-
- // Fill in default values.
- $display += array(
- 'label' => 'above',
- 'type' => $field_type['default_formatter'],
- 'settings' => array(),
- 'weight' => 0,
- );
- if ($display['type'] != 'hidden') {
- $formatter_type = field_info_formatter_types($display['type']);
- // Fallback to default formatter if formatter type is not available.
- if (!$formatter_type) {
- $display['type'] = $field_type['default_formatter'];
- $formatter_type = field_info_formatter_types($display['type']);
- }
- $display['module'] = $formatter_type['module'];
- // Fill in default settings for the formatter.
- $display['settings'] += field_info_formatter_settings($display['type']);
- }
-
- return $display;
+ $cache = _field_info_field_cache();
+ return $cache->prepareInstanceDisplay($display, $field['type']);
}
/**
* Prepares widget specifications for the current run-time context.
*
- * @param $field
- * The field structure for the instance.
- * @param $widget
- * Widget specifications as found in $instance['widget'].
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1880666.
+ *
+ * @see FieldInfo::prepareInstanceWidget()
*/
function _field_info_prepare_instance_widget($field, $widget) {
- $field_type = field_info_field_types($field['type']);
-
- // Fill in default values.
- $widget += array(
- 'type' => $field_type['default_widget'],
- 'settings' => array(),
- 'weight' => 0,
- );
-
- $widget_type = field_info_widget_types($widget['type']);
- // Fallback to default formatter if formatter type is not available.
- if (!$widget_type) {
- $widget['type'] = $field_type['default_widget'];
- $widget_type = field_info_widget_types($widget['type']);
- }
- $widget['module'] = $widget_type['module'];
- // Fill in default settings for the widget.
- $widget['settings'] += field_info_widget_settings($widget['type']);
-
- return $widget;
+ $cache = _field_info_field_cache();
+ return $cache->prepareInstanceWidget($widget, $field['type']);
}
/**
* Prepares 'extra fields' for the current run-time context.
*
- * @param $extra_fields
- * The array of extra fields, as collected in hook_field_extra_fields().
- * @param $entity_type
- * The entity type.
- * @param $bundle
- * The bundle name.
+ * The functionality has moved to the FieldInfo class. This function is kept as
+ * a backwards-compatibility layer. See http://drupal.org/node/1880666.
+ *
+ * @see FieldInfo::prepareExtraFields()
*/
function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) {
- $entity_type_info = entity_get_info($entity_type);
- $bundle_settings = field_bundle_settings($entity_type, $bundle);
- $extra_fields += array('form' => array(), 'display' => array());
-
- $result = array();
- // Extra fields in forms.
- foreach ($extra_fields['form'] as $name => $field_data) {
- $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
- if (isset($settings['weight'])) {
- $field_data['weight'] = $settings['weight'];
- }
- $result['form'][$name] = $field_data;
- }
-
- // Extra fields in displayed entities.
- $data = $extra_fields['display'];
- foreach ($extra_fields['display'] as $name => $field_data) {
- $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
- $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
- foreach ($view_modes as $view_mode) {
- if (isset($settings[$view_mode])) {
- $field_data['display'][$view_mode] = $settings[$view_mode];
- }
- else {
- $field_data['display'][$view_mode] = array(
- 'weight' => $field_data['weight'],
- 'visible' => TRUE,
- );
- }
- }
- unset($field_data['weight']);
- $result['display'][$name] = $field_data;
- }
-
- return $result;
+ $cache = _field_info_field_cache();
+ return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle);
}
/**
@@ -584,21 +440,50 @@ function field_info_bundles($entity_type = NULL) {
}
/**
+ * Returns a lightweight map of fields across bundles.
+ *
+ * The function only returns active, non deleted fields.
+ *
+ * @return
+ * An array keyed by field name. Each 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.
+ */
+function field_info_field_map() {
+ $cache = _field_info_field_cache();
+ return $cache->getFieldMap();
+}
+
+/**
* Returns all field definitions.
*
+ * Use of this function should be avoided when possible, since it loads and
+ * statically caches a potentially large array of information. Use
+ * field_info_field_map() instead.
+ *
+ * When iterating over the fields present in a given bundle after a call to
+ * field_info_instances($entity_type, $bundle), it is recommended to use
+ * 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.
+ *
+ * @see field_info_field_map()
*/
function field_info_fields() {
+ $cache = _field_info_field_cache();
+ $info = $cache->getFields();
+
$fields = array();
- $info = _field_info_collate_fields();
- foreach ($info['fields'] as $key => $field) {
+ foreach ($info as $key => $field) {
if (!$field['deleted']) {
$fields[$field['field_name']] = $field;
}
}
+
return $fields;
}
@@ -620,10 +505,8 @@ function field_info_fields() {
* @see field_info_field_by_id()
*/
function field_info_field($field_name) {
- $info = _field_info_collate_fields();
- if (isset($info['field_ids'][$field_name])) {
- return $info['fields'][$info['field_ids'][$field_name]];
- }
+ $cache = _field_info_field_cache();
+ return $cache->getField($field_name);
}
/**
@@ -641,17 +524,19 @@ function field_info_field($field_name) {
* @see field_info_field()
*/
function field_info_field_by_id($field_id) {
- $info = _field_info_collate_fields();
- if (isset($info['fields'][$field_id])) {
- return $info['fields'][$field_id];
- }
+ $cache = _field_info_field_cache();
+ return $cache->getFieldById($field_id);
}
/**
* Returns the same data as field_info_field_by_id() for every field.
*
- * This function is typically used when handling all fields of some entities
- * to avoid thousands of calls to field_info_field_by_id().
+ * Use of this function should be avoided when possible, since it loads and
+ * statically caches a potentially large array of information.
+ *
+ * When iterating over the fields present in a given bundle after a call to
+ * field_info_instances($entity_type, $bundle), it is recommended to use
+ * field_info_field() on each individual field instead.
*
* @return
* An array, each key is a field ID and the values are field arrays as
@@ -662,41 +547,57 @@ function field_info_field_by_id($field_id) {
* @see field_info_field_by_id()
*/
function field_info_field_by_ids() {
- $info = _field_info_collate_fields();
- return $info['fields'];
+ $cache = _field_info_field_cache();
+ return $cache->getFields();
}
/**
* Retrieves information about field instances.
*
+ * Use of this function to retrieve instances across separate bundles (i.e.
+ * when the $bundle parameter is NULL) should be avoided when possible, since
+ * it loads and statically caches a potentially large array of information. Use
+ * field_info_field_map() instead.
+ *
+ * When retrieving the instances of a specific bundle (i.e. when both
+ * $entity_type and $bundle_name are provided), the function also populates a
+ * static cache with the corresponding field definitions, allowing fast
+ * retrieval of field_info_field() later in the request.
+ *
* @param $entity_type
- * The entity type for which to return instances.
+ * (optional) The entity type for which to return instances.
* @param $bundle_name
- * The bundle name for which to return instances.
+ * (optional) The bundle name for which to return instances. If $entity_type
+ * is NULL, the $bundle_name parameter is ignored.
*
* @return
* If $entity_type is not set, return all instances keyed by entity type and
* bundle name. If $entity_type is set, return all instances for that entity
* type, keyed by bundle name. If $entity_type and $bundle_name are set, return
* all instances for that bundle.
+ *
+ * @see field_info_field_map()
*/
function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
- $info = _field_info_collate_fields();
+ $cache = _field_info_field_cache();
- if (isset($entity_type) && isset($bundle_name)) {
- return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array();
+ if (!isset($entity_type)) {
+ return $cache->getInstances();
}
- elseif (isset($entity_type)) {
- return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array();
- }
- else {
- return $info['instances'];
+ if (!isset($bundle_name)) {
+ return $cache->getInstances($entity_type);
}
+
+ return $cache->getBundleInstances($entity_type, $bundle_name);
}
/**
* Returns an array of instance data for a specific field and bundle.
*
+ * The function populates a static cache with all fields and instances used in
+ * the bundle, allowing fast retrieval of field_info_field() or
+ * field_info_instance() later in the request.
+ *
* @param $entity_type
* The entity type for the instance.
* @param $field_name
@@ -709,9 +610,10 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
* NULL if the instance does not exist.
*/
function field_info_instance($entity_type, $field_name, $bundle_name) {
- $info = _field_info_collate_fields();
- if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) {
- return $info['instances'][$entity_type][$bundle_name][$field_name];
+ $cache = _field_info_field_cache();
+ $info = $cache->getBundleInstances($entity_type, $bundle_name);
+ if (isset($info[$field_name])) {
+ return $info[$field_name];
}
}
@@ -769,11 +671,10 @@ function field_info_instance($entity_type, $field_name, $bundle_name) {
* The array of pseudo-field elements in the bundle.
*/
function field_info_extra_fields($entity_type, $bundle, $context) {
- $info = _field_info_collate_fields();
- if (isset($info['extra_fields'][$entity_type][$bundle][$context])) {
- return $info['extra_fields'][$entity_type][$bundle][$context];
- }
- return array();
+ $cache = _field_info_field_cache();
+ $info = $cache->getBundleExtraFields($entity_type, $bundle);
+
+ return isset($info[$context]) ? $info[$context] : array();
}
/**
diff --git a/modules/field/field.install b/modules/field/field.install
index 34d28073d..a4b153481 100644
--- a/modules/field/field.install
+++ b/modules/field/field.install
@@ -460,5 +460,12 @@ function field_update_7002() {
}
/**
+ * Add the FieldInfo class to the class registry.
+ */
+function field_update_7003() {
+ // Empty update to force a rebuild of the registry.
+}
+
+/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff --git a/modules/field/field.module b/modules/field/field.module
index 65dd48cfd..4331cdf42 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -873,7 +873,8 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
if ($field = field_info_field($field_name)) {
if (is_array($display)) {
// When using custom display settings, fill in default values.
- $display = _field_info_prepare_instance_display($field, $display);
+ $cache = _field_info_field_cache();
+ $display = $cache->prepareInstanceDisplay($display, $field["type"]);
}
// Hook invocations are done through the _field_invoke() functions in
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index a75619427..c639f38f2 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -324,11 +324,14 @@ function field_sql_storage_field_storage_delete_field($field) {
* Implements hook_field_storage_load().
*/
function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) {
- $field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
- $field = $field_info[$field_id];
+ // 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);
diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test
index 8004178eb..9cd8535b5 100644
--- a/modules/field/tests/field.test
+++ b/modules/field/tests/field.test
@@ -1144,6 +1144,16 @@ class FieldInfoTestCase extends FieldTestCase {
$this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.');
$instances = field_info_instances('user', 'user');
$this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array.");
+
+ // Test that querying for invalid entity types does not add entries in the
+ // list returned by field_info_instances().
+ field_info_cache_clear();
+ field_info_instances('invalid_entity', 'invalid_bundle');
+ // Simulate new request by clearing static caches.
+ drupal_static_reset();
+ field_info_instances('invalid_entity', 'invalid_bundle');
+ $instances = field_info_instances();
+ $this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before');
}
/**
@@ -1254,6 +1264,80 @@ class FieldInfoTestCase extends FieldTestCase {
}
/**
+ * Test field_info_field_map().
+ */
+ function testFieldMap() {
+ // We will overlook fields created by the 'standard' install profile.
+ $exclude = field_info_field_map();
+
+ // Create a new bundle for 'test_entity' entity type.
+ field_test_create_bundle('test_bundle_2');
+
+ // Create a couple fields.
+ $fields = array(
+ array(
+ 'field_name' => 'field_1',
+ 'type' => 'test_field',
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'type' => 'hidden_test_field',
+ ),
+ );
+ foreach ($fields as $field) {
+ field_create_field($field);
+ }
+
+ // Create a couple instances.
+ $instances = array(
+ array(
+ 'field_name' => 'field_1',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ ),
+ array(
+ 'field_name' => 'field_1',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle_2',
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'entity_type' => 'test_cacheable_entity',
+ 'bundle' => 'test_bundle',
+ ),
+ );
+ foreach ($instances as $instance) {
+ field_create_instance($instance);
+ }
+
+ $expected = array(
+ 'field_1' => array(
+ 'type' => 'test_field',
+ 'bundles' => array(
+ 'test_entity' => array('test_bundle', 'test_bundle_2'),
+ ),
+ ),
+ 'field_2' => array(
+ 'type' => 'hidden_test_field',
+ 'bundles' => array(
+ 'test_entity' => array('test_bundle'),
+ 'test_cacheable_entity' => array('test_bundle'),
+ ),
+ ),
+ );
+
+ // Check that the field map is correct.
+ $map = field_info_field_map();
+ $map = array_diff_key($map, $exclude);
+ $this->assertEqual($map, $expected);
+ }
+
+ /**
* Test that the field_info settings convenience functions work.
*/
function testSettingsInfo() {
@@ -1277,6 +1361,31 @@ class FieldInfoTestCase extends FieldTestCase {
$this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
}
}
+
+ /**
+ * Tests that the field info cache can be built correctly.
+ */
+ function testFieldInfoCache() {
+ // Create a test field and ensure it's in the array returned by
+ // field_info_fields().
+ $field_name = drupal_strtolower($this->randomName());
+ $field = array(
+ 'field_name' => $field_name,
+ 'type' => 'test_field',
+ );
+ field_create_field($field);
+ $fields = field_info_fields();
+ $this->assertTrue(isset($fields[$field_name]), '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
+ // field_test_entity_info(). Ensure the test field is still in the returned
+ // array.
+ field_info_cache_clear();
+ variable_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.');
+ }
}
class FieldFormTestCase extends FieldTestCase {
@@ -2179,6 +2288,41 @@ class FieldCrudTestCase extends FieldTestCase {
}
/**
+ * Tests reading field definitions.
+ */
+ function testReadFields() {
+ $field_definition = array(
+ 'field_name' => 'field_1',
+ 'type' => 'test_field',
+ );
+ field_create_field($field_definition);
+
+ // 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.');
+
+ // 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'));
+ $this->assertTrue(empty($fields), 'No field was found.');
+
+ // Create an instance of the field.
+ $instance_definition = array(
+ 'field_name' => $field_definition['field_name'],
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ );
+ field_create_instance($instance_definition);
+
+ // Check that criteria spanning over the field_config_instance table work.
+ $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle']));
+ $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
+ $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name']));
+ $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
+ }
+
+ /**
* Test creation of indexes on data column.
*/
function testFieldIndexes() {
diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc
index 95af3eeba..c6686ebc2 100644
--- a/modules/field/tests/field_test.entity.inc
+++ b/modules/field/tests/field_test.entity.inc
@@ -9,6 +9,12 @@
* Implements hook_entity_info().
*/
function field_test_entity_info() {
+ // If requested, clear the field cache while this hook is being called. See
+ // testFieldInfoCache().
+ if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) {
+ field_info_cache_clear();
+ }
+
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
$test_entity_modes = array(
'full' => array(
diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module
index 93cbcccc7..5f8bc45ef 100644
--- a/modules/field_ui/field_ui.module
+++ b/modules/field_ui/field_ui.module
@@ -332,23 +332,30 @@ function _field_ui_bundle_admin_path($entity_type, $bundle_name) {
* Identifies inactive fields within a bundle.
*/
function field_ui_inactive_instances($entity_type, $bundle_name = NULL) {
- if (!empty($bundle_name)) {
- $inactive = array($bundle_name => array());
- $params = array('bundle' => $bundle_name);
+ $params = array('entity_type' => $entity_type);
+
+ if (empty($bundle_name)) {
+ $active = field_info_instances($entity_type);
+ $inactive = array();
}
else {
- $inactive = array();
- $params = array();
+ // Restrict to the specified bundle. For consistency with the case where
+ // $bundle_name is NULL, the $active and $inactive arrays are keyed by
+ // bundle name first.
+ $params['bundle'] = $bundle_name;
+ $active = array($bundle_name => field_info_instances($entity_type, $bundle_name));
+ $inactive = array($bundle_name => array());
}
- $params['entity_type'] = $entity_type;
- $active_instances = field_info_instances($entity_type);
+ // Iterate on existing definitions, and spot those that do not appear in the
+ // $active list collected earlier.
$all_instances = field_read_instances($params, array('include_inactive' => TRUE));
foreach ($all_instances as $instance) {
- if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) {
+ if (!isset($active[$instance['bundle']][$instance['field_name']])) {
$inactive[$instance['bundle']][$instance['field_name']] = $instance;
}
}
+
if (!empty($bundle_name)) {
return $inactive[$bundle_name];
}
diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test
index d0a822a82..b67b70e2a 100644
--- a/modules/field_ui/field_ui.test
+++ b/modules/field_ui/field_ui.test
@@ -269,7 +269,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
*/
function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
// Reset the fields info.
- _field_info_collate_fields(TRUE);
+ field_info_cache_clear();
// Assert field settings.
$field = field_info_field($field_name);
$this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.'));
@@ -360,7 +360,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
$this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type);
// Reset the fields info.
- _field_info_collate_fields(TRUE);
+ field_info_cache_clear();
// Check that the field instance was deleted.
$this->assertNull(field_info_instance('node', $this->field_name, $this->type), t('Field instance was deleted.'));
// Check that the field was not deleted
@@ -370,7 +370,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
$this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2);
// Reset the fields info.
- _field_info_collate_fields(TRUE);
+ field_info_cache_clear();
// Check that the field instance was deleted.
$this->assertNull(field_info_instance('node', $this->field_name, $type_name2), t('Field instance was deleted.'));
// Check that the field was deleted too.