Drupal 7 Services List and Entity Reference Field Issues

I've been working on an angularjs project that features a Drupal services backend. As I'm new to angular (or was), I thought I had to have been doing something wrong posting data back to Drupal to save my user accounts. The response from the services backend would be a 406, with the messages:

406 (Not Acceptable : An illegal choice has been detected. Please contact the site administrator.)

Actually, you'll have multiple copies of "An illegal choice has been detected. Please contact the site administrator." depending on the number of list and entity reference fields you are posting. The Drupal logs on the backend will contain multiple copies as well, with one of two additions per.

Warning: Illegal offset type in isset or empty in list_field_validate()
(line 394 of modules/field/modules/list/list.module)

Warning: Illegal offset type in isset or empty in _form_validate()
(line 1368 of includes/form.inc).

It took a while to track down what was happening. It made no sense that I could not post back the user object I had just received via api. After an hour googling, I came across this little hint: http://drupal.stackexchange.com/questions/61124/drupal-7-services-3-inse.... The author explains that he was experiencing the same issue, but found his own solution. He modified the data being posted to services to represent the format of data found the node form array, not in the end structure resolved. I'm not sure he was aware that was what he was doing, but it lead me to further searching. I came across a bug report against the core for Drupal that explained why the data format the previous author found worked. It seems services is designed to use a somewhat disused part of Drupal for its save operations. It uses version of drupal_form_submit to allow validation in a generic fashion. This lets them get the errors seen above easily, without knowing what modules are actually at work. The issue in doing that is the data structure is not the real node, it's the form, which loops back to the bug.

From what I can tell, both services, and the core seem responsible for this bug. Not really sure which should fix it, arguments could be made for both. The list module is part of core, services is its own animal, and entity reference is yet another responsible party. Honestly, the chance of this being fixed before my project needed to be done is minimal, and likely for yours as well if you're reading this. So, I worked out my own solution, perhaps services, and entity reference could add it to their code base, I'll link them here for reference. But for you and I, we can use services to fix services!

There's a lovely api hook in services that allows us to modify arguments before they are passed to controllers: hook_services_request_preprocess_alter.
So, here's some experimental code I've produced that fixes both list module select, and entity reference select fields. It does leave a note in the logs if it rewrites a field so make sure check them if you find any odd field based behaviors.

  1. /**
  2.  * Allow to alter arguments before they are passed to service callback.
  3.  *
  4.  * @param $controller
  5.  *   Controller definition
  6.  * @param $args
  7.  *   Array of arguments
  8.  * @param $options
  9.  *
  10.  * @see services_controller_execute()
  11.  * @see services.runtime.inc
  12.  */
  13. function custom_services_request_preprocess_alter($controller, &$args, $options) {
  14.   // Look for node and user callbacks to correct the argument format automatically
  15.  
  16.   // No callback? No work
  17.   if( empty($controller['callback']) ){
  18.     return;
  19.   }
  20.  
  21.   // Not one of ours? Skip it
  22.   if( !in_array($controller['callback'], array(
  23.     '_user_resource_create',
  24.     '_user_resource_update',
  25.     '_node_resource_create',
  26.     '_node_resource_update',
  27.   ))){
  28.     return;
  29.   }
  30.  
  31.   // Determine the entity type
  32.   if( $controller['callback'] == '_user_resource_update' || $controller['callback'] == '_node_resource_update' ){
  33.     $entity =& $args[1];
  34.   } else {
  35.     $entity =& $args[0];
  36.   }
  37.   $language = !empty($entity['language'])? $entity['language'] : LANGUAGE_NONE;
  38.  
  39.   // Get a field dump of properties associated with this entity type
  40.   $fields = field_info_fields();
  41.  
  42.   // Modify the input of all incoming data for fields of type entity_reference
  43.   // or using the select widget
  44.   foreach( $fields as $field_name => $field_settings ){
  45.     // Skip non-problematic fields
  46.     if( !in_array($field_settings['module'], array('list', 'entityreference')) ){
  47.       continue;
  48.     }
  49.  
  50.     // Skip fields that are not present in the posted data
  51.     if( !array_key_exists($field_name, $entity) ){
  52.       continue;
  53.     }
  54.  
  55.     switch($field_settings['module']){
  56.       case 'list':
  57.         if( empty($entity[$field_name][$language][0]) ){
  58.           continue;
  59.         }
  60.         $values = array();
  61.         foreach( $entity[$field_name][$language] as $field_item ){
  62.           $values[] = $field_item['value'];
  63.         }
  64.         $entity[$field_name][$language] = $values;
  65.  
  66.         watchdog(
  67.           'services'
  68.           ,t('Entity %field_name argument rewritten by custom programming. <pre>!data</pre>')
  69.           ,array(
  70.                 '%field_name' => $field_name,
  71.                 '!data' => print_r($entity[$field_name], TRUE),
  72.           )
  73.           ,WATCHDOG_INFO
  74.         );
  75.         break;
  76.         case 'entityreference':
  77.           if( empty($entity[$field_name][$language][0]['target_id']) ){
  78.           continue;
  79.         }
  80.         $values = array();
  81.         foreach( $entity[$field_name][$language] as $field_item ){
  82.           $values[] = $field_item['target_id'];
  83.         }
  84.         $entity[$field_name][$language] = $values;
  85.  
  86.         watchdog(
  87.           'services'
  88.           ,t('Entity %field_name argument rewritten by custom programming. <pre>!data</pre>')
  89.           ,array(
  90.                 '%field_name' => $field_name,
  91.                 '!data' => print_r($entity[$field_name], TRUE),
  92.           )
  93.           ,WATCHDOG_INFO
  94.         );
  95.         break;
  96.     }
  97.   }
  98.  
  99. }
Tags: 

Comments

I've posted a abbreviated version of this on services' issue list. You can follow it here: https://www.drupal.org/node/2400075

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.