summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2009-05-17 00:32:29 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2009-05-17 00:32:29 +0000
commitf180449767d54b14d024732f5ec1dd659b0b4a8d (patch)
treefe02b61b461c4e18edbb1125fd46fead487106b0
parentba61b42bd94d0c6af0de1fcba42d60bf3ceeb4c2 (diff)
downloadbrdo-f180449767d54b14d024732f5ec1dd659b0b4a8d.tar.gz
brdo-f180449767d54b14d024732f5ec1dd659b0b4a8d.tar.bz2
#362024 by neclimdul, yched, and bjaspan: Make hook_field_load() multiple like field_attach_load().
-rw-r--r--modules/field/field.api.php61
-rw-r--r--modules/field/field.attach.inc234
-rw-r--r--modules/field/field.autoload.inc25
-rw-r--r--modules/field/field.test133
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module19
-rw-r--r--modules/simpletest/tests/field_test.module64
6 files changed, 354 insertions, 182 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 7971899b8..130562858 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -188,18 +188,32 @@ function hook_field_formatter_info() {
/**
* Define custom load behavior for this module's field types.
*
+ * Unlike other field hooks, this hook operates on multiple objects. The
+ * $objects, $instances and $items parameters are arrays keyed by object id.
+ * For performance reasons, information for all available objects should be
+ * loaded in a single query where possible.
+ *
+ * Note that the changes made to the field values get cached by the
+ * field cache for subsequent loads.
+ *
* @param $obj_type
* The type of $object.
- * @param $object
- * The object for the operation.
+ * @param $objects
+ * Array of objects being loaded, keyed by object id.
* @param $field
* The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
+ * @param $instances
+ * Array of instance structures for $field for each object, keyed by object id.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * Array of field values already loaded for the objects, keyed by object 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 object.
+ * @return
+ * Changes or additions to field values are done by altering the $items
+ * parameter by reference.
*/
-function hook_field_load($obj_type, $object, $field, $instance, $items) {
+function hook_field_load($obj_type, $objects, $field, $instances, &$items, $age) {
}
/**
@@ -452,23 +466,20 @@ function hook_field_attach_form($obj_type, $object, &$form, &$form_state) {
* @param $obj_type
* The type of objects for which to load fields; e.g. 'node' or 'user'.
* @param $objects
- * An array of objects for which to load fields. The keys for primary id and
- * bundle name to load are identified by hook_fieldable_info for $obj_type.
+ * An array of objects for which to load fields, keyed by object 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 object.
- * @param $additions
- * An array of field data for the objects being loaded, keyed by entity id,
- * field name, and item delta number.
* @param $skip_fields
* An array keyed by names of fields whose data has already been loaded and
* therefore should not be loaded again. The values associated to these keys
* are not specified.
* @return
- * Loaded field values are added to $additions and loaded field names are set
- * as keys in $skip_fields.
+ * - Loaded field values are added to $objects. Fields with no values should be
+ * set as an empty array.
+ * - Loaded field names are set as keys in $skip_fields.
*/
-function hook_field_attach_pre_load($obj_type, $objects, $age, &$additions, &$skip_fields) {
+function hook_field_attach_pre_load($obj_type, &$objects, $age, &$skip_fields) {
}
/**
@@ -476,10 +487,17 @@ function hook_field_attach_pre_load($obj_type, $objects, $age, &$additions, &$sk
*
* This hook is invoked after the field module has performed the operation.
*
+ * Unlike other field_attach hooks, this hook accounts for 'multiple loads'.
+ * It takes an array of objects indexed by object id as its first parameter.
+ * For performance reasons, information for all available objects should be
+ * loaded in a single query where possible.
+ *
+ * Note that the changes made to the objects' field values get cached by the
+ * field cache for subsequent loads.
+ *
* See field_attach_load() for details and arguments.
- * TODO: Currently, this hook only accepts a single object a time.
*/
-function hook_field_attach_load($obj_type, $object) {
+function hook_field_attach_load($obj_type, &$objects, $age) {
}
/**
@@ -640,10 +658,9 @@ function hook_field_attach_delete_bundle($bundle, $instances) {
* Load field data for a set of objects.
*
* @param $obj_type
- * The entity type of objects being loaded, such as 'node' or
- * 'user'.
+ * The entity type of object, such as 'node' or 'user'.
* @param $objects
- * The array of objects for which to load data.
+ * The array of objects for which to load data, keyed by object 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
@@ -653,10 +670,10 @@ function hook_field_attach_delete_bundle($bundle, $instances) {
* therefore should not be loaded again. The values associated to these keys
* are not specified.
* @return
- * An array of field data for the objects, keyed by entity id, field
- * name, and item delta number.
+ * Loaded field values are added to $objects. Fields with no values should be
+ * set as an empty array.
*/
-function hook_field_storage_load($obj_type, $queried_objs, $age, $skip_fields) {
+function hook_field_storage_load($obj_type, &$objects, $age, $skip_fields) {
}
/**
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index a6fbdf0a9..92bcbb068 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -102,7 +102,7 @@ define('FIELD_STORAGE_INSERT', 'insert');
* for any object after the operation is complete, and access or
* modify all the field, form, or display data for that object and
* operation. For example, field_attach_view() invokes
- * hook_field_attach_view_alter(). These all-module hooks are distinct from
+ * hook_field_attach_view_alter(). These all-module hooks are distinct from
* those of the Field Types API, such as hook_field_load(), that are
* only invoked for the module that defines a specific field type.
*
@@ -127,8 +127,7 @@ define('FIELD_STORAGE_INSERT', 'insert');
* Invoke a field hook.
*
* @param $op
- * - Possible operations include:
- * - load
+ * Possible operations include:
* - form
* - validate
* - presave
@@ -140,28 +139,21 @@ define('FIELD_STORAGE_INSERT', 'insert');
* - view
* - preprocess
* - prepare translation
- *
* @param $obj_type
- * - Can be:
- * - node
- * - user
- * - Others not yet implemented.
- *
+ * The type of $object; e.g. 'node' or 'user'.
* @param $object
- * - The fully formed $obj_type object.
- *
+ * The fully formed $obj_type object.
* @param $a
- * - The $form in the 'form' operation.
- * - The value of $teaser in the 'view' operation.
- * - Otherwise NULL.
- *
+ * - The $form in the 'form' operation.
+ * - The value of $teaser in the 'view' operation.
+ * - Otherwise NULL.
* @param $b
- * - The $form_state in the 'submit' operation.
- * - Otherwise NULL.
+ * - The $form_state in the 'submit' operation.
+ * - Otherwise NULL.
*
* @param $default
- * - TRUE: render the default field implementation of the field hook.
- * - FALSE: render the field module's implementation of the field hook.
+ * - TRUE: use the default field implementation of the field hook.
+ * - FALSE: use the field module's implementation of the field hook.
*/
function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $default = FALSE) {
list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
@@ -179,11 +171,11 @@ function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $defaul
if (is_array($result)) {
$return = array_merge($return, $result);
}
- else if (isset($result)) {
+ elseif (isset($result)) {
$return[] = $result;
}
}
- // Populate $items back in the field values, but avoid replacing missing
+ // Populate field values back in the object, but avoid replacing missing
// fields with an empty array (those are not equivalent on update).
if ($items !== array() || property_exists($object, $field_name)) {
$object->$field_name = $items;
@@ -194,6 +186,85 @@ function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $defaul
}
/**
+ * Invoke a field operation across fields on multiple objects.
+ *
+ * @param $op
+ * Possible operations include:
+ * - load
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $objects
+ * An array of objects, keyed by object id.
+ * @param $a
+ * - The $age parameter in the 'load' operation.
+ * - Otherwise NULL.
+ * @param $b
+ * Currently always NULL.
+ * @param $default
+ * - TRUE: use the default field implementation of the field hook.
+ * - FALSE: use the field module's implementation of the field hook.
+ * @return
+ * An array of returned values keyed by object id.
+ */
+function _field_invoke_multiple($op, $obj_type, &$objects, &$a = NULL, &$b = NULL, $default = FALSE) {
+ $fields = array();
+ $grouped_instances = array();
+ $grouped_objects = array();
+ $grouped_items = array();
+ $return = array();
+
+ // Preparation:
+ // - Get the list of fields contained in the various bundles.
+ // - For each field, group the corresponding instances, objects and field
+ // values.
+ // - Initialize the return value for each object.
+ foreach ($objects as $object) {
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ foreach (field_info_instances($bundle) as $instance) {
+ $field_name = $instance['field_name'];
+ if (!isset($grouped_fields[$field_name])) {
+ $fields[$field_name] = field_info_field($field_name);
+ }
+ $grouped_instances[$field_name][$id] = $instance;
+ $grouped_objects[$field_name][$id] = &$objects[$id];
+ $grouped_items[$field_name][$id] = isset($object->$field_name) ? $object->$field_name : array();
+ }
+ $return[$id] = array();
+ }
+
+ // Call each field's operation.
+ foreach ($fields as $field_name => $field) {
+ if (!empty($field)) {
+ $function = $default ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
+ if (drupal_function_exists($function)) {
+ $results = $function($obj_type, $grouped_objects[$field_name], $field, $grouped_instances[$field_name], $grouped_items[$field_name], $a, $b);
+ // Merge results by object.
+ if (isset($results)) {
+ foreach ($results as $id => $result) {
+ if (is_array($result)) {
+ $return[$id] = array_merge($return[$id], $result);
+ }
+ else {
+ $return[$id][] = $result;
+ }
+ }
+ }
+ }
+ }
+
+ // Populate field values back in the objects, but avoid replacing missing
+ // fields with an empty array (those are not equivalent on update).
+ foreach ($grouped_objects[$field_name] as $id => $object) {
+ if ($grouped_items[$field_name][$id] !== array() || property_exists($object, $field_name)) {
+ $object->$field_name = $grouped_items[$field_name][$id];
+ }
+ }
+ }
+
+ return $return;
+}
+
+/**
* Invoke field.module's version of a field hook.
*/
function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL) {
@@ -201,6 +272,13 @@ function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL)
}
/**
+ * Invoke field.module's version of a field hook on multiple objects.
+ */
+function _field_invoke_multiple_default($op, $obj_type, &$objects, &$a = NULL, &$b = NULL) {
+ return _field_invoke_multiple($op, $obj_type, $objects, $a, $b, TRUE);
+}
+
+/**
* @} End of "defgroup field_attach"
*
* The rest of the functions in this file are not in a group, but
@@ -239,113 +317,79 @@ function _field_attach_form($obj_type, $object, &$form, $form_state) {
* objects of a single object type.
*
* @param $obj_type
- * The type of objects for which to load fields; e.g. 'node' or
- * 'user'.
+ * The type of $object; e.g. 'node' or 'user'.
* @param $objects
- * An array of objects for which to load fields. The keys for
- * primary id and bundle name to load are identified by
- * hook_fieldable_info for $obj_type.
+ * An array of objects for which to load fields, keyed by object id.
+ * Each object needs to have its 'bundle key', 'id key' and (if applicable)
+ * 'revision key' filled.
* @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 object. Defaults to FIELD_LOAD_CURRENT; use
* field_attach_load_revision() instead of passing FIELD_LOAD_REVISION.
* @returns
- * On return, the objects in $objects are modified by having the
- * appropriate set of fields added.
+ * Loaded field values are added to $objects. Fields with no values should be
+ * set as an empty array.
*/
function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
$queried_objects = array();
+ $info = field_info_fieldable_types($obj_type);
+ $cacheable = isset($info['cacheable']) ? $info['cacheable'] : FALSE;
+
// Fetch avaliable objects from cache.
foreach ($objects as $object) {
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
- $cid = "field:$obj_type:$id:$vid";
- if ($cacheable && $cached = cache_get($cid, 'cache_field')) {
- foreach ($cached->data as $key => $value) {
- $object->$key = $value;
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ if ($cacheable && $cached = cache_get("field:$obj_type:$id:$vid", 'cache_field')) {
+ foreach ($cached->data as $field_name => $items) {
+ $object->$field_name = $items;
}
}
else {
- $queried_objects[$id] = $objects[$id];
+ $queried_objects[$id] = $object;
}
}
// Fetch other objects from the database.
if ($queried_objects) {
- // The loading order is:
+ // The invoke order is:
// - hook_field_attach_pre_load()
// - storage engine's hook_field_storage_load()
- // - field-type modules hook_field_load()
+ // - field-type module's hook_field_load()
// - hook_field_attach_load()
- // We need the raw additions to be able to cache them, so the hooks must
- // not alter objects directly but return their additions. At each step,
- // results are merged into the $queried_objects, and into the $additions
- // array, that will eventually get cached.
// Invoke hook_field_attach_pre_load(): let any module load field
// data before the storage engine, accumulating along the way.
- $additions_pre_load = array();
$skip_fields = array();
foreach (module_implements('field_attach_pre_load') as $module) {
$function = $module . '_field_attach_pre_load';
- $function($obj_type, $queried_objects, $age, $additions_pre_load, $skip_fields);
+ $function($obj_type, $queried_objects, $age, $skip_fields);
}
// Invoke the storage engine's hook_field_storage_load(): the field storage
// engine loads the rest.
- $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age, $skip_fields);
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age, $skip_fields);
- // First, merge the additions from the storage engine.
- foreach ($additions as $id => $obj_additions) {
- foreach ($obj_additions as $key => $value) {
- $queried_objects[$id]->$key = $value;
- }
- }
- // Then, merge the pre_load additions, so that they take precedence.
- foreach ($additions_pre_load as $id => $obj_additions) {
- foreach ($obj_additions as $key => $value) {
- $queried_objects[$id]->$key = $value;
- $additions[$id][$key] = $value;
- }
+ // Invoke field-type module's hook_field_load().
+ _field_invoke_multiple('load', $obj_type, $queried_objects, $age);
+
+ // Invoke hook_field_attach_load(): let other modules act on loading the
+ // object.
+ foreach (module_implements('field_attach_load') as $module) {
+ $function = $module . '_field_attach_load';
+ $function($obj_type, $queried_objects, $age);
}
- // TODO D7 : to be consistent we might want to make hook_field_load() accept
- // multiple objects too. Which forbids going through _field_invoke(), but
- // requires manually iterating the instances instead.
- foreach ($queried_objects as $id => $object) {
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
-
- // Make sure empty fields are present as empty arrays.
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- if (!isset($object->{$instance['field_name']})) {
- $queried_objects[$id]->{$instance['field_name']} = array();
- $additions[$id][$instance['field_name']] = array();
+ // Build cache data.
+ if ($cacheable) {
+ foreach ($queried_objects as $id => $object) {
+ $data = array();
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $instance) {
+ $data[$instance['field_name']] = $queried_objects[$id]->{$instance['field_name']};
}
- }
-
- // Invoke field-type modules hook_field_load().
- $custom_additions = _field_invoke('load', $obj_type, $object);
- foreach ($custom_additions as $key => $value) {
- $queried_objects[$id]->$key = $value;
- $additions[$id][$key] = $value;
- }
-
- // Invoke hook_field_attach_load(): let other modules act on loading the
- // object.
- // TODO : this currently doesn't get cached (we cache $additions).
- // This should either be called after we fetch from cache, or return an
- // array of additions.
- foreach (module_implements('field_attach_load') as $module) {
- $function = $module . '_field_attach_load';
- $function($obj_type, $queried_objects[$id]);
- }
-
- // Cache the data.
- if ($cacheable) {
$cid = "field:$obj_type:$id:$vid";
- $data = isset($additions[$id]) ? $additions[$id] : array();
cache_set($cid, $data, 'cache_field');
}
}
@@ -356,13 +400,15 @@ function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
* Load all fields for a previous version of each of a set of
* objects of a single object type.
*
+ * Loading different versions of the same objects is not supported,
+ * and should be done by separate calls to the function.
+ *
* @param $obj_type
- * The type of objects for which to load fields; e.g. 'node' or
- * 'user'.
+ * The type of $object; e.g. 'node' or 'user'.
* @param $objects
- * An array of objects for which to load fields. The keys for
- * primary id, revision id, and bundle name to load are identified by
- * hook_fieldable_info for $obj_type.
+ * An array of objects for which to load fields, keyed by object id.
+ * Each object needs to have its 'bundle key', 'id key' and 'revision key'
+ * filled.
* @returns
* On return, the objects in $objects are modified by having the
* appropriate set of fields added.
diff --git a/modules/field/field.autoload.inc b/modules/field/field.autoload.inc
index 21a8947de..3ea8754cd 100644
--- a/modules/field/field.autoload.inc
+++ b/modules/field/field.autoload.inc
@@ -41,20 +41,19 @@ function field_attach_form($obj_type, $object, &$form, $form_state) {
* objects of a single object type.
*
* @param $obj_type
- * The type of objects for which to load fields; e.g. 'node' or
- * 'user'.
+ * The type of $object; e.g. 'node' or 'user'.
* @param $objects
- * An array of objects for which to load fields. The keys for
- * primary id and bundle name to load are identified by
- * hook_fieldable_info for $obj_type.
+ * An array of objects for which to load fields, keyed by object id.
+ * Each object needs to have its 'bundle key', 'id key' and (if applicable)
+ * 'revision key' filled.
* @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 object. Defaults to FIELD_LOAD_CURRENT; use
* field_attach_load_revision() instead of passing FIELD_LOAD_REVISION.
* @returns
- * On return, the objects in $objects are modified by having the
- * appropriate set of fields added.
+ * Loaded field values are added to $objects. Fields with no values should be
+ * set as an empty array.
*
* This function is an autoloader for _field_attach_load() in modules/field/field.attach.inc.
*/
@@ -67,13 +66,15 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
* Load all fields for a previous version of each of a set of
* objects of a single object type.
*
+ * Loading different versions of the same objects is not supported,
+ * and should be done by separate calls to the function.
+ *
* @param $obj_type
- * The type of objects for which to load fields; e.g. 'node' or
- * 'user'.
+ * The type of $object; e.g. 'node' or 'user'.
* @param $objects
- * An array of objects for which to load fields. The keys for
- * primary id, revision id, and bundle name to load are identified by
- * hook_fieldable_info for $obj_type.
+ * An array of objects for which to load fields, keyed by object id.
+ * Each object needs to have its 'bundle key', 'id key' and 'revision key'
+ * filled.
* @returns
* On return, the objects in $objects are modified by having the
* appropriate set of fields added.
diff --git a/modules/field/field.test b/modules/field/field.test
index 8af8aa55b..fe0e3e869 100644
--- a/modules/field/field.test
+++ b/modules/field/field.test
@@ -47,15 +47,6 @@ class FieldAttachTestCase extends DrupalWebTestCase {
field_create_instance($this->instance);
}
-// function testFieldAttachLoadMultiple() {
- // TODO : test the 'multiple' aspect of load:
- // define 2 bundles, 3 fields
- // bundle1 gets instances of field1, field2
- // bundle2 gets instances of field1, field3
- // load 2 entities (one for each bundle) in a single load
- // check that everything gets loaded ok.
-// }
-
/**
* Check field values insert, update and load.
*
@@ -63,6 +54,11 @@ class FieldAttachTestCase extends DrupalWebTestCase {
* updates random field data and then loads and verifies the data.
*/
function testFieldAttachSaveLoad() {
+ // Configure the instance so that we test hook_field_load() (see
+ // field_test_field_load() in field_test.module).
+ $this->instance['settings']['test_hook_field_load'] = TRUE;
+ field_update_instance($this->instance);
+
$entity_type = 'test_entity';
$values = array();
@@ -96,6 +92,8 @@ class FieldAttachTestCase extends DrupalWebTestCase {
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// The field value loaded matches the one inserted or updated.
$this->assertEqual($entity->{$this->field_name}[$delta]['value'] , $values[$current_revision][$delta]['value'], t('Currrent revision: expected value %delta was found.', array('%delta' => $delta)));
+ // The value added in hook_field_load() is found.
+ $this->assertEqual($entity->{$this->field_name}[$delta]['additional_key'], 'additional_value', t('Currrent revision: extra information for value %delta was found', array('%delta' => $delta)));
}
// Confirm each revision loads the correct data.
@@ -107,6 +105,73 @@ class FieldAttachTestCase extends DrupalWebTestCase {
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// The field value loaded matches the one inserted or updated.
$this->assertEqual($entity->{$this->field_name}[$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
+ // The value added in hook_field_load() is found.
+ $this->assertEqual($entity->{$this->field_name}[$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta)));
+ }
+ }
+ }
+
+ /**
+ * Test the 'multiple' load feature.
+ */
+ function testFieldAttachLoadMultiple() {
+ $entity_type = 'test_entity';
+
+ // Define 2 bundles.
+ $bundles = array(
+ 1 => 'test_bundle_1',
+ 2 => 'test_bundle_2',
+ );
+ field_test_create_bundle($bundles[1]);
+ field_test_create_bundle($bundles[2]);
+ // Define 3 fields:
+ // - field_1 is in bundle_1 and bundle_2,
+ // - field_2 is in bundle_1,
+ // - field_3 is in bundle_2.
+ $field_bundles_map = array(
+ 1 => array(1, 2),
+ 2 => array(1),
+ 3 => array(2),
+ );
+ for ($i = 1; $i <= 3; $i++) {
+ $field_names[$i] = 'field_' . $i;
+ $field = array('field_name' => $field_names[$i], 'type' => 'test_field');
+ field_create_field($field);
+ foreach ($field_bundles_map[$i] as $bundle) {
+ $instance = array(
+ 'field_name' => $field_names[$i],
+ 'bundle' => $bundles[$bundle],
+ 'settings' => array(
+ // Configure the instance so that we test hook_field_load()
+ // (see field_test_field_load() in field_test.module).
+ 'test_hook_field_load' => TRUE,
+ ),
+ );
+ field_create_instance($instance);
+ }
+ }
+
+ // Create one test entity per bundle, with random values.
+ foreach ($bundles as $index => $bundle) {
+ $entities[$index] = field_test_create_stub_entity($index, $index, $bundle);
+ $entity = clone($entities[$index]);
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $field_name => $instance) {
+ $values[$index][$field_name] = mt_rand(1, 127);
+ $entity->$field_name = array(array('value' => $values[$index][$field_name]));
+ }
+ field_attach_insert($entity_type, $entity);
+ }
+
+ // Check that a single load correctly loads field values for both entities.
+ field_attach_load($entity_type, $entities);
+ foreach ($entities as $index => $entity) {
+ $instances = field_info_instances($bundles[$index]);
+ foreach ($instances as $field_name => $instance) {
+ // The field value loaded matches the one inserted.
+ $this->assertEqual($entity->{$field_name}[0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
+ // The value added in hook_field_load() is found.
+ $this->assertEqual($entity->{$field_name}[0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
}
}
}
@@ -354,7 +419,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Create a new bundle. This has to be initiated by the module so that its
// hook_fieldable_info() is consistent.
$new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
- field_test_create_bundle($new_bundle, $this->randomName());
+ field_test_create_bundle($new_bundle);
// Add an instance to that bundle.
$this->instance['bundle'] = $new_bundle;
@@ -394,7 +459,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Create a new bundle. This has to be initiated by the module so that its
// hook_fieldable_info() is consistent.
$new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
- field_test_create_bundle($new_bundle, $this->randomName());
+ field_test_create_bundle($new_bundle);
// Add an instance to that bundle.
$this->instance['bundle'] = $new_bundle;
@@ -449,48 +514,58 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted");
}
+ /**
+ * Test field cache.
+ */
function testFieldAttachCache() {
- // Create a revision
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ // Initialize random values and a test entity.
+ $entity_init = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
$values = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$delta]['value'] = mt_rand(1, 127);
}
- $entity->{$this->field_name} = $values;
$noncached_type = 'test_entity';
$cached_type = 'test_cacheable_entity';
- // Non-cached type:
+
+ // Test non-cached object type.
$cid = "field:$noncached_type:0:0";
- // Confirm no initial cache entry
+ // Confirm no initial cache entry.
$this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry');
- // Save, and confirm no cache entry
+ // Save, and confirm no cache entry.
+ $entity = clone($entity_init);
+ $entity->{$this->field_name} = $values;
field_attach_insert($noncached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save');
- // Load, and confirm no cache entry
+ // Load, and confirm no cache entry.
+ $entity = clone($entity_init);
field_attach_load($noncached_type, array(0 => $entity));
$this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load');
- // Cached type:
+
+ // Test cached object type.
$cid = "field:$cached_type:0:0";
- // Confirm no initial cache entry
+ // Confirm no initial cache entry.
$this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry');
- // Save, and confirm no cache entry
+ // Save, and confirm no cache entry.
+ $entity = clone($entity_init);
+ $entity->{$this->field_name} = $values;
field_attach_insert($cached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
- // Load, and confirm cache entry
+ // Load, and confirm cache entry.
+ $entity = clone($entity_init);
field_attach_load($cached_type, array(0 => $entity));
$cache = cache_get($cid, 'cache_field');
$this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load');
- // Delete, and confirm no cache entry
+ // Delete, and confirm no cache entry.
field_attach_delete($cached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
}
@@ -1156,6 +1231,8 @@ class FieldInstanceTestCase extends DrupalWebTestCase {
field_create_instance($this->instance_definition);
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$field_type = field_info_field_types($this->field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+ $formatter_type = field_info_formatter_types($instance['display']['full']['type']);
// Check that default values are set.
$this->assertIdentical($instance['required'], FALSE, t('Required defaults to false.'));
@@ -1163,21 +1240,17 @@ class FieldInstanceTestCase extends DrupalWebTestCase {
$this->assertIdentical($instance['description'], '', t('Description defaults to empty string.'));
// Check that default instance settings are set.
- $settings = array('test_instance_setting' => 'dummy test string');
- $this->assertIdentical($settings, $instance['settings'] , t('Default instance settings have been written.'));
+ $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.'));
// Check that the widget is the default one.
$this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Default widget has been written.'));
// Check that default widget settings are set.
- $settings = array('test_widget_setting' => 'dummy test string');
- $this->assertIdentical($settings, $instance['widget']['settings'] , t('Default widget settings have been written.'));
+ $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.'));
// Check that we have display info for 'full' build_mode.
$this->assertTrue(isset($instance['display']['full']), t('Display for "full" build_mode has been written.'));
// Check that the formatter is the default one.
$this->assertIdentical($instance['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.'));
// Check that the default formatter settings are set.
- $info = field_info_formatter_types($instance['display']['full']['type']);
- $settings = $info['settings'];
- $this->assertIdentical($settings, $instance['display']['full']['settings'] , t('Default formatter settings for "full" build_mode have been written.'));
+ $this->assertIdentical($instance['display']['full']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" build_mode have been written.'));
// Guarantee that the field/bundle combination is unique.
try {
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 4d17110d0..2589b66ab 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -176,7 +176,7 @@ function field_sql_storage_field_storage_delete_field($field_name) {
/**
* Implementation of hook_field_storage_load().
*/
-function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_fields = array()) {
+function field_sql_storage_field_storage_load($obj_type, &$objects, $age, $skip_fields = array()) {
$etid = _field_sql_storage_etid($obj_type);
$load_current = $age == FIELD_LOAD_CURRENT;
@@ -185,18 +185,16 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
$delta_count = array();
foreach ($objects as $obj) {
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj);
- foreach (field_info_instances($bundle) as $instance) {
- $field_ids[$instance['field_name']][] = $load_current ? $id : $vid;
- $delta_count[$id][$instance['field_name']] = 0;
+ foreach (field_info_instances($bundle) as $field_name => $instance) {
+ if (!isset($skip_fields[$field_name])) {
+ $objects[$id]->{$field_name} = array();
+ $field_ids[$field_name][] = $load_current ? $id : $vid;
+ $delta_count[$id][$field_name] = 0;
+ }
}
}
- $additions = array();
foreach ($field_ids as $field_name => $ids) {
- if (isset($skip_fields[$field_name])) {
- continue;
- }
-
$field = field_info_field($field_name);
$table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name);
@@ -218,12 +216,11 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
}
// Add the item to the field values for the entity.
- $additions[$row->entity_id][$field_name][] = $item;
+ $objects[$row->entity_id]->{$field_name}[] = $item;
$delta_count[$row->entity_id][$field_name]++;
}
}
}
- return $additions;
}
/**
diff --git a/modules/simpletest/tests/field_test.module b/modules/simpletest/tests/field_test.module
index 95110792a..79b15e28b 100644
--- a/modules/simpletest/tests/field_test.module
+++ b/modules/simpletest/tests/field_test.module
@@ -83,14 +83,31 @@ function field_test_fieldable_info() {
);
}
-function field_test_create_bundle($bundle, $text) {
+/**
+ * Create a new bundle for test_entity objects.
+ *
+ * @param $bundle
+ * The machine-readable name of the bundle.
+ * @param $text
+ * The human-readable name of the bundle. If none is provided, the machine
+ * name will be used.
+ */
+function field_test_create_bundle($bundle, $text = NULL) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
- $bundles += array($bundle => $text);
+ $bundles += array($bundle => $text ? $text : $bundle);
variable_set('field_test_bundles', $bundles);
field_attach_create_bundle($bundle);
}
+/**
+ * Rename a bundle for test_entity objects.
+ *
+ * @param $bundle_old
+ * The machine-readable name of the bundle to rename.
+ * @param $bundle_new
+ * The new machine-readable name of the bundle.
+ */
function field_test_rename_bundle($bundle_old, $bundle_new) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
$bundles[$bundle_new] = $bundles[$bundle_old];
@@ -100,6 +117,12 @@ function field_test_rename_bundle($bundle_old, $bundle_new) {
field_attach_rename_bundle($bundle_old, $bundle_new);
}
+/**
+ * Delete a bundle for test_entity objects.
+ *
+ * @param $bundle
+ * The machine-readable name of the bundle to delete.
+ */
function field_test_delete_bundle($bundle) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
unset($bundles[$bundle]);
@@ -311,9 +334,13 @@ function field_test_field_info() {
return array(
'test_field' => array(
'label' => t('Test Field'),
- 'description' => t('Stores the value 1.'),
- 'settings' => array('test_field_setting' => 'dummy test string'),
- 'instance_settings' => array('test_instance_setting' => 'dummy test string'),
+ 'settings' => array(
+ 'test_field_setting' => 'dummy test string',
+ ),
+ 'instance_settings' => array(
+ 'test_instance_setting' => 'dummy test string',
+ 'test_hook_field_load' => FALSE,
+ ),
'default_widget' => 'test_field_widget',
'default_formatter' => 'field_test_default',
),
@@ -333,13 +360,6 @@ function field_test_field_columns($field) {
}
/**
- * Implementation of hook_instance_settings().
- */
-function field_test_field_instance_settings($field_type) {
- return array('test_instance_setting' => 'dummy test string');
-}
-
-/**
* Implementation of hook_field_validate().
*
* Possible error codes:
@@ -488,6 +508,24 @@ function field_test_field_formatter_info() {
}
/**
+ * Implementation of hook_field_load().
+ */
+function field_test_field_load($obj_type, $objects, $field, $instances, &$items, $age) {
+ foreach ($items as $id => $item) {
+ // To keep the test non-intrusive, only act for instances with the
+ // test_hook_field_load setting explicitly set to TRUE.
+ if ($instances[$id]['settings']['test_hook_field_load']) {
+ foreach ($item as $delta => $value) {
+ // Don't add anything on empty values.
+ if ($value) {
+ $items[$id][$delta]['additional_key'] = 'additional_value';
+ }
+ }
+ }
+ }
+}
+
+/**
* Implementation of hook_theme().
*/
function field_test_theme() {
@@ -530,4 +568,4 @@ function theme_field_formatter_field_test_multiple($element) {
*/
function field_test_default_value($obj_type, $object, $field, $instance) {
return array(array('value' => 99));
-} \ No newline at end of file
+}