5

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());
4
  • Just to confirm - are the changes you made to 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?
    – Clive
    Commented Oct 14, 2013 at 17:04
  • You might want to look at EntityMetadataWrapper::validate(), which hooks into a validation callback that you can presumably define on your field type/individual properties. Docs are sketchy though, you're right.
    – Clive
    Commented Oct 14, 2013 at 17:08
  • Yes, working in a php test page, when I just do entity_load('some id') and then dsm() the result, I can see all the field columns and their values. However, when I pass the loaded entity to entity_metadata_wrapper, it says that the data type in invalid, which doesn't make sense, because the data is the entity I just loaded with entity_load(). I'll add the code I'm using to the original post; maybe I'm using it incorrectly.
    – ouni
    Commented Oct 14, 2013 at 18:51
  • Ok, my problem was that entity_load() returned an array; fixed. But when I wrap that entity, try setting the field value, and then do getPropertyInfo(), all I see is a bunch of metadata about the field, and I can no longer access the field values themselves, even though they were accessible before being wrapped.
    – ouni
    Commented Oct 14, 2013 at 19:47

2 Answers 2

0

To access a field value from the wrapper try:

$new_ent->field_device_id->meid_hex->value();

or for the whole entity: $entity_object = $new_ent->value();

0

You can try the following:

$node = node_load($nid);
$wrapper = entity_metadata_wrapper('node', $node);
//- Set Lang
$wrapper->language->set('en');
//- Set Title
$wrapper->title->set('test');
//- Set Body Value
$wrapper->body->set(array('value' => 'Some text', 'format' => 'full_html'));
//- Set Custom field 
$wrapper->your_custom_field_name->set('field value');
$wrapper->save();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.