diff options
-rw-r--r-- | modules/field/field.api.php | 61 | ||||
-rw-r--r-- | modules/field/field.attach.inc | 234 | ||||
-rw-r--r-- | modules/field/field.autoload.inc | 25 | ||||
-rw-r--r-- | modules/field/field.test | 133 | ||||
-rw-r--r-- | modules/field/modules/field_sql_storage/field_sql_storage.module | 19 | ||||
-rw-r--r-- | modules/simpletest/tests/field_test.module | 64 |
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 +} |