I have a simple custom module that creates a new field type. The field has multiple columns in the database; the idea is that a user (or code) will enter a single value, and then that value is converted into several different formats and saved to the database columns.
Currently, this is working by using a method from the book Drupal 7 Module Development, where the user’s original value is converted using a widget validate function.
It works great when a human is filling out a form; but the problem is, when I try to create an entity in code, using entity_metadata_wrapper, there’s no widget, and thus, no widget validate function runs.
It looks like I need to look at hook_field_info and specify a property_type of ‘struct’ with a custom setter callback, but I can’t find any documentation that describes how to use the property or getter/setter callbacks.
How can I use entity metadata wrappers with custom field database columns?
/** * Implements hook_field_info(). */ function device_id_field_info() { return array( 'device_id' => array( 'label' => t('Device ID'), 'description' => t('Converts the input device ID and saves it to the database in various formats.'), 'default_widget' => 'device_id_simple', 'default_formatter' => 'device_id_raw', 'default_token_formatter' => 'device_id_combined', 'property_type' => 'device_id', 'property_callback' => array('_device_id_property_callback'), ), ); } /** * Callback to alter the property info of device id fields. * * @see device_id_field_info(). */ function _device_id_property_callback(&$info, $entity_type, $field, $instance, $field_type) { $name = $field['field_name']; $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name]; $property['type'] = ($field['cardinality'] != 1) ? 'list<deviceid>' : 'deviceid'; $property['getter callback'] = 'entity_metadata_field_verbatim_get'; $property['setter callback'] = 'entity_metadata_field_verbatim_set'; $property['property info'] = device_id_property_info(); unset($property['query callback']); } /** * Defines info for the properties of the device ID field data structure. */ function device_id_property_info($name = NULL) { // Build an array of basic property information for the device ID field. $properties = array( 'raw_id' => array( 'label' => t('Raw input ID'), ), 'esn_hex' => array( 'label' => t('Converted ESN in hexadecimal format'), ), 'esn_dec' => array( 'label' => t('Converted ESN in decimal format'), ), 'meid_hex' => array( 'label' => t('Converted MEID in hexadecimal format'), ), 'meid_dec' => array( 'label' => t('Converted MEID in decimal format'), ), 'imei_hex' => array( 'label' => t('Converted IMEI in hexadecimal format'), ), 'imei_dec' => array( 'label' => t('Converted IMEI in decimal format'), ), 'iccid_hex' => array( 'label' => t('Converted ICCID in hexadecimal format'), ), 'iccid_dec' => array( 'label' => t('Converted ICCID in decimal format'), ), ); } /** * Implements hook_field_is_empty(). * * Pseudo-hook. */ function device_id_field_is_empty($item, $field) { if ($field['type'] == 'device_id') { if ( empty($item['raw_id']) && empty($item['cap_dec']) && empty($item['cap_hex']) && empty($item['meid_dec']) && empty($item['meid_hex']) && empty($item['imei_dec']) && empty($item['imei_hex']) && empty($item['iccid_dec']) && empty($item['iccid_hex'])) { return TRUE; } } return FALSE; } /** * Implements hook_field_widget_info(). */ function device_id_field_widget_info() { return array( 'device_id_simple' => array( 'label' => t('Device ID text field'), 'description' => t( 'Allow the user to enter the device ID, and display it in whatever format it was entered'), 'field types' => array('device_id'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), ); } /** * Implements hook_field_widget_form(). * * Pseudo hook **/ function device_id_field_widget_form( &$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { switch ($instance['widget']['type'] == 'device_id_simple') { case 'device_id_simple': $element += array( '#element_validate' => array('_device_id_wrapper_validate'), 'device_id_wrapper' => array( 'input_id' => array( '#title' => t('Device ID'), '#type' => 'textfield', '#default_value' => isset($items[$delta]['raw_id']) ? $items[$delta]['raw_id'] : '', ), ) ); break; } return $element; } function _device_id_wrapper_validate($element, &$form_state) { //This function is also called when submitting the field //configuration form. If so, skip validation since it //won't work anyway if ($form_state['complete form']['#form_id'] == 'field_ui_field_edit_form') { return; } $values = $form_state['values']; $language = $element['#language']; $field_name = $element['#field_name']; $item = $values[$field_name][$language][$element['#delta']]['device_id_wrapper']; foreach ($values[$field_name][$language] as $delta => $item) { if (is_array($item)) { if (array_key_exists('device_id_wrapper', $item)) { $conversion = new deviceIDConversion($item['device_id_wrapper']['input_id']); $conversion_values = $conversion->calculate(); if ($conversion_values) { $new_values = array( 'esn_hex' => isset($conversion_values['ESN_HEX']) ? $conversion_values['ESN_HEX'] : NULL, 'esn_dec' => isset($conversion_values['ESN_DEC']) ? $conversion_values['ESN_DEC'] : NULL, 'meid_hex' => isset($conversion_values['MEID_HEX']) ? $conversion_values['MEID_HEX'] : NULL, 'meid_dec' => isset($conversion_values['MEID_DEC']) ? $conversion_values['MEID_DEC'] : NULL, 'imei_hex' => isset($conversion_values['IMEI_HEX']) ? $conversion_values['IMEI_HEX'] : NULL, 'imei_dec' => isset($conversion_values['IMEI_DEC']) ? $conversion_values['IMEI_DEC'] : NULL, 'iccid_hex' => isset($conversion_values['ICCID_HEX']) ? $conversion_values['ICCID_HEX'] : NULL, 'iccid_dec' => isset($conversion_values['ICCID_DEC']) ? $conversion_values['ICCID_DEC'] : NULL, 'raw_id' => $item['device_id_wrapper']['input_id'], ); form_set_value($element, $new_values, $form_state); } else { form_set_error($field_name, t('Your device ID doesn't match a valid format. Check the device ID, verify it's correct, and type/scan it again.')); } } } } } /** * Implements hook_field_validate(). * * Pseudo-hook. */ function device_id_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { foreach ($items as $delta => $item) { if (!empty($item['raw_id'])) { $conversion = new deviceIDConversion($item['raw_id']); $conversion_values = $conversion->calculate(); if (!$conversion_values) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'device_id_invalid_format', 'message' => t('Your device ID doesn't match a valid format. Check the device ID, verify it's correct, and type/scan it again.'), ); } } } }
Here is some example code I’m using in a test page to work with this content; when I do entity_load(), I can view the contents of the custom field, including the database columns I defined in hook_info(). When I wrap this in entity_metadata_wrapper() and try setting the field value, nothing happens. Also, after wrapping, I can no longer see the field values, even with getPropertyInfo().
$ent = entity_load('node', array('2955')); $new_ent = entity_metadata_wrapper('node', $ent['2955']); $new_ent->field_device_id->meid_hex = 'a1000002747883'; $new_ent->save(); dsm($new_ent->getPropertyInfo());
Sponsored by SupremePR