Calculate field collection values based on associated node values


This is an example snippet of how you can calculate and store a value in a field collection based on values present both in the node and the field collection. In this example I am building a league management site and to relate things in Views and EVA appropriately, as well as combine date information without it being a pain to enter for users. To do both of these, there are essentially two hidden, calculated fields in the field_collection_item.

Date combination - This takes a date from the node (think of this as a scheduled event) and combines it with a time field from the field_collection_item. It then stores this new combined value in the field_collection item as field_date_time (to form something like Apr 1st 2013 - 10:00pm) and adds a user defined length of games for the "to" value (in date, value2). This allows for users to enter the date and then enter the times as two separate actions instead of having to enter the full date and time each game starts and then the full date and time each game ends.

Entity reference combination - I needed to combine two entity references into a single one (home and away team separately, as well as a way of querying that a team is related off of 1 field for contextual arguments). This allows for listing correctly who the home and away team are in a view, as well as limiting the list of items based on if a team is listed in either category. This type of OR logic isn't available to the Views contextual filters operator and hence this solution. This looks at the field_collection home and away field and if they are set, combines the array values in a new field called field_teams. This field is hidden as the date field is so users never know it's there but it's always set and updated thanks to the node_insert and node_update hooks mirroring one another.

Special note:
The most important part in this operation is at the end with $game->save(TRUE);. If you perform $game->save(), the operation will go into an infinite loop as field_collections trigger update hooks on their parents w/ save (the default option to only save this item is FALSE). This propagation triggers the game node to be updated again and hence the loop we form. If you pass it TRUE in the save function, this will ONLY update the field_collection_item in question and not everything associated to it. This can also be useful when dealing with nested-field-collections though this example does not tackle this.

Get raw version
  1. /**
  2.  * Implements hook_node_insert().
  3.  */
  4. function league_glue_node_insert($node) {
  5. // ensure this mirrors the update routine
  6. league_glue_node_update($node);
  7. }
  9. /**
  10.  * Implements hook_node_insert().
  11.  */
  12. function league_glue_node_update($node) {
  13. // only run for the game day node type that has games defined
  14. if ($node->type == 'league_game_day' && is_array($node->field_games['und'])) {
  15. foreach ($node->field_games['und'] as $item) {
  16. $game = entity_load_single('field_collection_item', $item['value']);
  17. // need to make field_date_time the combination of node and FC time values
  18. $new_time = $node->field_date['und'][0]['value'] + $game->field_start['und'][0]['value'];
  19. // start
  20. $game->field_date_time['und'][0]['value'] = $new_time;
  21. // end based on global game length settings
  22. $game->field_date_time['und'][0]['value2'] = $new_time + variable_get('league_game_length', LEAGUE_GAME_LENGTH);
  23. // same approach but to combine 2 entity references into 1
  24. $teams = array();
  25. if (isset($game->field_home_team['und'])) {
  26. $teams[] = array('target_id' => $game->field_home_team['und'][0]['target_id']);
  27. }
  28. if (isset($game->field_away_team['und'])) {
  29. $teams[] = array('target_id' => $game->field_away_team['und'][0]['target_id']);
  30. }
  31. $game->field_teams = array('und' => $teams);
  32. // save, ensuring ONLY this FC instance is updated
  33. $game->save(TRUE);
  34. }
  35. }
  36. }