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());
hook_field_info()
working from a 'get' perspective? Are you able to access (for example)$node_wrapper->field_device_id[0]->esn_hex->value()
if you load an existing entity and inspect it?EntityMetadataWrapper::validate()
, which hooks into avalidation callback
that you can presumably define on your field type/individual properties. Docs are sketchy though, you're right.