Creating field collection programatically

Comments

tcalin’s picture

Hello!
I am trying to create a field_collection from the code (a line on an invoice to be more exactly)
The sequence looks like this:

  $some_node = node_load($nid);
  $fc_values = array();
  $fc_values['field_name'] = 'name_of_the_field';
  $fc_values['is_new'] = 1;
  ......
  $fc_values['others'] = 'values';

  $fc = new FieldCollectionItemEntity($fc_values);

  $fc->setHostEntity('node', $some_node);
  $fc->save();

but I get the following exception from save function:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '122' for key 'PRIMARY'

Am I missing something?
Thanks in advance!

ogi’s picture

subscribe

ogi’s picture

Status: Active » Fixed

This worked for me:

// $node->nid must be valid at this point
// $node->field_my_field_collection is the field collection field

$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_my_field_collection'));
$field_collection_item->setHostEntity('node', $node);
$field_collection_item->field_a_field_collection_field[LANGUAGE_NONE][]['value'] = ...;
...
$field_collection_item->save();

$node->field_my_field_collection[LANGUAGE_NONE][]['value'] = $field_collection_item->item_id;
tcalin’s picture

Thanks!
I finally got the time to test it and it worked for me too.
Anyway, my code should also be OK.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

tcalin’s picture

This solution does not work anymore.

rbruhn’s picture

Thought I would post my solution for programmatically creating a field collection, along with an attached field, during a module install. The example is specific to my use, but easily modified to add the field types you need. Of course, if there is an easier way please feel free to add your thoughts. This works with the current 7.x-1.x-dev branch.

/**
 * Create a Field Collection field and attach collection node reference
 */
function _create_field_collection() {
  $t = get_t();
  
  $fields_array = array(
    array(
      'field' => array(
        'field_name' => 'bir_collection',
        'label' => $t('BIR Collection Nodes'),
        'cardinality' => -1,
        'type' => 'field_collection',
      ),
      'instance' => array(
        'field_name' => 'bir_collection',
        'entity_type' => 'node',
        'bundle' => 'article',
        'label' => $t('BIR Collection Nodes'),
        'description' => '',
        'widget' => array('type' => 'field_collection_embed'),
        'required' => 1,
      ),
    ),
    array(
      'field' => array(
        'field_name' => 'bir_collection_node',
        'type' => 'node_reference',
        'label' => '',
        'cardinality' => 1,
        'settings' => array(
          'referenceable_types' => array(
            'bir_specimen' => 'bir_specimen',
            'bir_image' => 'bir_image',
            'bir_locality' => 'bir_locality',
            'bir_view' => 'bir_view',
          ),
        ),
      ),
      'instance' => array(
        'field_name' => 'bir_collection_node',
        'entity_type' => 'field_collection_item',
        'bundle' => 'bir_collection',
        'label' => '',
        'cardinality' => 1,
        'description' => '',
        'widget' => array('type' => 'node_reference_autocomplete'),
        'display' => array('default' => array('type' => 'node_reference_default')),
      )
    ),
  );
  
  // Loop through fields array and create field and instance
  foreach ($fields_array as $field) {
    // Check if field already exists
    if (!field_info_field($field['field']['field_name'])) {
      field_create_field($field['field']);
    }

    // Check if instance exists
    if (!field_info_instance($field['instance']['entity_type'], $field['instance']['field_name'], $field['instance']['bundle'])) {
      field_create_instance($field['instance']);
    }
  }  
}

And as stated above, this worked when needing to add data programmatically.

// Create node
$node = new stdClass();
$node->type = 'article';
node_object_prepare($node);
$node->title = 'Testing Article Creation';
$node->language = LANGUAGE_NONE;
$node->status = 1;
$node->uid = 1;
node_save($node);

// Create and save field collection for node
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'bir_collection'));
$field_collection_item->setHostEntity('node', $node);
$field_collection_item->bir_collection_node[LANGUAGE_NONE][]['nid'] = 99999;
$field_collection_item->save();
phreestilr’s picture

Thanks! Very helpful

jibize’s picture

Thank you!!

JMOmandown’s picture

@rbruhn: Sorry to reactivate an old thread. I was curious if you knew of any issues with field collection creation in the traditional field array method as you have described when in a batch operation. The following throws a HTML general error and has had me stumped for the past few days.

<?php
// Create Both the Aircraft Utility Field Collection in the Commerce Order Entity
function batch_aircraft_commerce_collection2($aid, $aml, $nodeid, $max, $operation_details, &$context)
{
    $id = $aid;
    $name = 'field_' . $aid;
$t = get_t();

$collected = array(
        array(
            'field' => array(
                'field_name' => $name,
                'label' => $t('test'),
                'cardinality' => -1,
                'type' => 'field_collection',
            ),
            'instance' => array(
                'field_name' => $name,
                'entity_type' => 'commerce_order',
                'bundle' => 'commerce_order',
                'label' => $t('test'),
                'description' => '',
                'widget' => array('type' => 'field_collection_embed'),
                'required' => 1,
            ),
        ),
    );

// Loop through fields array and create field and instance
  foreach ($collected as $field) {
    // Check if field already exists
    if (!field_info_field($field['field']['field_name'])) {
      field_create_field($field['field']);
    }

    // Check if instance exists
    if (!field_info_instance($field['instance']['entity_type'], $field['instance']['field_name'], $field['instance']['bundle'])) {
      field_create_instance($field['instance']);
    }
  } 
}
?>
rbruhn’s picture

@JMOmandown - Well, I took your code above and changed a few things so I could run it in a test.php file using drush on my system. The code you posted is simply attempting to create Field Collections, and not attaching fields to those collections. So I'm assuming that is what you wanted. In the code below:
1) I changed the entity_type and bundle because I do not have commerce installed.
2) I refactored the code since you are only creating one Field Collection at a time when calling the function. So there is really no need to use a foreach loop if you don't have to.
3) As well, since I'm merely testing the function, I removed the other unneeded variables being passed to the function so it would not throw an error while running the test script. I know you are doing this in a batch so yours is different, but perhaps the below will help you find out what is wrong.

Anyway, the below creates the Field Collections for me. I see them added in my UI, as well as being present on the Article node form.
Hope this helps.

// Create Both the Aircraft Utility Field Collection in the Commerce Order Entity
function batch_aircraft_commerce_collection2($aid) {
  $id = $aid;
  $name = 'field_' . $aid;
  $t = get_t();

  $collection = array();
  $collection['field'] = array(
    'field_name' => $name,
    'label' => $t('test'),
    'cardinality' => -1,
    'type' => 'field_collection',
  );

  $collection['instance'] = array(
    'field_name' => $name,
    'entity_type' => 'node',
    'bundle' => 'article',
    'label' => $t('test'),
    'description' => '',
    'widget' => array('type' => 'field_collection_embed'),
    'required' => 1,
  );

  if (!field_info_field($collection['field']['field_name'])) {
    field_create_field($collection['field']);
  }

  // Check if instance exists
  if (!field_info_instance($collection['instance']['entity_type'], $collection['instance']['field_name'], $collection['instance']['bundle'])) {
    field_create_instance($collection['instance']);
  }
}

$test = array(123,1234,12345,123456);
foreach ($test as $aid) {
  batch_aircraft_commerce_collection2($aid);
}
JMOmandown’s picture

@rbruhn: Thanks for the quick response. As I feared it is still not functional which is so weird to me. If I comment out the function above (one of many operations in the batch) then the batch runs smoothly. With it enabled I get and error with no repsonse text:

<?php
An AJAX HTTP error occurred. HTTP Result Code: 500 Debugging information follows. Path: /batch?id=483&op=do StatusText: Service unavailable (with message) ResponseText:
?>

So weird, but I thank you for your quick response and attempt.

rbruhn’s picture

@JMOmandown: Yes, I hate those messages that don't really give you anything to work with. I think they are trying to fix some of those in D8. When I run into your problem, I use to place some code to spit out to a file or something so I can at least see at what point it's failing. For example, writing to file when it creates the field or instance and the name of the field. From there, following to each function called so I can get the exact point. I could usually figure out what is going wrong.

Might be possible to use drush to run your batch (http://drupal.org/node/873132) with debug enabled. Since I've started using drush, errors have been a lot easier to ascertain.

thtas’s picture

I'm having success with this method

//assuming $node exists
  $my_collection = entity_create('field_collection_item', array('field_name' => 'field_my_collection'));
  $my_collection->setHostEntity('node', $node);
  $my_collection->field_text_data[LANGUAGE_NONE][0]['value'] = "hello";
  $my_collection->field_term_ref[LANGUAGE_NONE][0]['tid'] = 123;
  $my_collection->field_node_ref[LANGUAGE_NONE][0]['target_id'] = 345;
  $my_collection->save();
  node_save($node);
dgtlmoon’s picture

To set multiple field collection values, just put the field_collection entity stuff in a loop

  $lorem = "Vestibulum ultricies dolor eget libero sagittis rhoncus. Nam mattis convallis libero, et volutpat est accumsan eu. Nulla id porttitor ligula, vitae eleifend nulla. <br />\n...";
  $titles = array("Some title", "Some other title");
  $query = db_query("SELECT nid, title FROM {node} WHERE type=:x and status=1", array(':x' => 'product'));
  foreach ($query as $row) {
    $node->field_product_description = array();
    foreach($titles as $title) {
      $my_collection = entity_create('field_collection_item', array('field_name' => 'field_product_description'));
      $my_collection->setHostEntity('node', $node);
      $my_collection->field_title[LANGUAGE_NONE][]['value'] = $title;
      $my_collection->field_description[LANGUAGE_NONE][] = array('format' => 2, 'value' => $lorem );
      $my_collection->save();
    }
    node_save($node);
 }

hope it helps someone!

codesmith’s picture

I just had an issue where trying to add a new field collections to the 'user' entity would delete any existing items. The solution was to directly load the user entity and not depend on the global $user as that one doesn't have the field collection information.

$thisUser = entity_load('user', array($user->uid));
$thisUser = $thisUser[$user->uid]; //make thisUser the user object, not an array of user objects

$newFieldCollectionItem = array();
$newFieldCollectionItem['field_name'] = 'field_download';
$newFieldCollectionItem['field_download_filename'][LANGUAGE_NONE][0]['value'] = $src;
$newFieldCollectionItem['field_download_date'][LANGUAGE_NONE][0]['value'] = time();

$newFieldCollection = entity_create('field_collection_item', $newFieldCollectionItem);
$newFieldCollection->setHostEntity('user', $thisUser);
$newFieldCollection->save();
Thomas83’s picture

first of all, English is not my native language so sorry if this reads bad.

I'm trying to program a field collection with field collection item fields like the code as given by rbruhn here: https://drupal.org/comment/4653514#comment-4653514

I want to create it on an easier leavel for testing purposes. My goal is to create a taxonomy reference dropdown and a textfield.

I manage to reproduce the field_collection type. Hower, I can't reproduce any field collections item fields. Let's say I want a textfield in my field collection.

creating an ordinary textfield field collection item field, this would make sense to me (but it doesn't work). Can someone tell me what I do wrong? Instead of appearing inside my field collection it appears as a field inside my node which contains my field collection field

array(
  'field' => array(
    'field_name' => 'name_of_my_field',
    'type' => 'text',
    ),
  ),
  'instance' => array(
    'field_name' => 'name_of_my_field',
    'entity_type' => 'field_collection_item',
    'bundle' => 'name_of_my_field_collection_field',,
    'widget' => array('type' => 'text_textfield'),
  )
),
pratip.ghosh’s picture

Issue summary: View changes

For anyone wondering how to save a fieldcollection data which is within another field collection data, it may come to some help...

$question_details = entity_load('field_collection_item', array($field_collection_id1));
$answer_collection = entity_create('field_collection_item', array('field_name' => 'field_quiz_answers'));
$answer_collection->setHostEntity('field_collection_item', $question_details);
$answer_collection->field_quiz_answer[LANGUAGE_NONE][0]['value'] = $answer;
$answer_collection->save();
$inserted_answer_id = $answer_collection->item_id;
petu’s picture

Here is the code for multiple values inside the field collection entity:

// Create a node with submission
  $node = new stdClass();
  $node->title = $title;
  $node->type = "article";
  node_object_prepare($node); // Sets some defaults. Invokes hook_prepare() and hook_node_prepare().
  $node->language = LANGUAGE_NONE; // Or e.g. 'en' if locale is enabled
  //$node->uid = $user->uid; // you can set the author for a node if you want
  $node->status = 1; //(1 or 0): published or not
  $node->promote = 0; //(1 or 0): promoted to front page
  $node->comment = 0; // 0 = comments disabled, 1 = read only, 2 = read/write
  $node = node_submit($node); // Prepare node for saving

  // field collection items.
  for ($i = 0; $i < $total_values; $i++) {
    $field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_answers'));
    $field_collection_item->setHostEntity('node', $node);
    $field_collection_item->field_question_body[LANGUAGE_NONE][] = array('value' => $questions[$i]);
    $field_collection_item->field_answer_body[LANGUAGE_NONE][] = array('value' => $answers[0]);
    $field_collection_item->save();
  }
  node_save($node);
jwilson3’s picture

Thanks to all previous commenters, this thread helped me get what I needed done. However I ran into error message #1822844: Notice: Undefined index: revision_id in field_collection_field_get_entity() (line 1586 of field_collection/field_collection.modu and found that if I used:

 $field_collection_item->save(TRUE);

Instead of:

 $field_collection_item->save();

The errors went away.

ethanLee’s picture

// Create a node with submission
  $node = new stdClass();
  $node->title = $title;
  $node->type = "article";
  node_object_prepare($node); // Sets some defaults. Invokes hook_prepare() and hook_node_prepare().
  $node->language = LANGUAGE_NONE; // Or e.g. 'en' if locale is enabled
  //$node->uid = $user->uid; // you can set the author for a node if you want
  $node->status = 1; //(1 or 0): published or not
  $node->promote = 0; //(1 or 0): promoted to front page
  $node->comment = 0; // 0 = comments disabled, 1 = read only, 2 = read/write
  $node = node_submit($node); // Prepare node for saving
  // field collection items.
  for ($i = 0; $i < $total_values; $i++) {
    $field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_answers'));
    $field_collection_item->setHostEntity('node', $node);
    $field_collection_item->field_question_body[LANGUAGE_NONE][] = array('value' => $questions[$i]);
    $field_collection_item->field_answer_body[LANGUAGE_NONE][] = array('value' => $answers[0]);
    $field_collection_item->save();
  }
  node_save($node);

Great solution for me. But I got a new problem. This method only save one record for $field_collection_item['und'][0]['value']. If I want to save $field_collection_item['und'][1]['value']. What should I do? Please help! Thank you.

DieterAtWork’s picture

For a good tutorial on how to actually do this, with explanations, look at this: https://www.drupal.org/node/1842304#comment-9295475

jienckebd’s picture

If anyone else needs something similar, the below script will make copies of any field collections on your site. In this case, it creates 2 copies for foreign languages (Spanish and Portuguese). It also adds field instances that were on the original field collection.

// loop through all field collections on the whole site
$field_collection_fields = db_query("SELECT * FROM {field_config} AS fc WHERE type = :type ORDER BY id ASC", array(':type' => 'field_collection'));
foreach ($field_collection_fields as $field_collection_field) {
  // load the full objects -- the DB query only returns raw data from the DB
  $field = field_info_field($field_collection_field->field_name);
  $instance_object = db_query("SELECT * FROM {field_config_instance} AS fi WHERE field_id = :field_id ORDER BY id ASC", array(':field_id' => $field['id']))->fetchObject();
  $instance = field_info_instance($instance_object->entity_type, $instance_object->field_name, $instance_object->bundle);
  
  // create new fields based on sp_ and pt_ prefixes
  $field_name_parts = explode('ield_', $field['field_name']);
  $field_sp = $field_pt = $field;
  $field_sp['field_name'] = 'field_sp_' . $field_name_parts[1];
  $field_pt['field_name'] = 'field_pt_' . $field_name_parts[1];
  
  // unset the id property so they're treated like new fields
  unset($field_sp['id']);
  unset($field_pt['id']);
  
  // create fields
  field_create_field($field_sp);
  field_create_field($field_pt);
  
  // now create instances to go along with the fields
  $instance_sp = $instance_pt = $instance;

  // prepare the slightly adjusted properties for saving
  $instance_sp['field_name'] = $field_sp['field_name'];
  $instance_pt['field_name'] = $field_pt['field_name'];
  $instance_sp['label'] = $instance['label'] . ' (Spanish)';
  $instance_pt['label'] = $instance['label'] . ' (Portuguese)';
  $instance_sp['widget']['weight'] = $instance['widget']['weight'] + 1; // SP field goes directly below original
  $instance_pt['widget']['weight'] = $instance['widget']['weight'] + 2; // PT field goes 2 weights below original
  
  // now save new field instances
  field_create_instance($instance_sp);
  field_create_instance($instance_pt);
  
  // save field instances that belong to each field collection
  $field_collection_instances = db_query("SELECT * FROM {field_config_instance} AS fc WHERE entity_type = :entity_type AND bundle = :bundle ORDER BY id ASC", array(':entity_type' => 'field_collection_item', ':bundle' => $field['field_name']));
  foreach ($field_collection_instances as $field_collection_instance) {
    $fc_subfield_instance = field_info_instance($field_collection_instance->entity_type, $field_collection_instance->field_name, $field_collection_instance->bundle);
    
    $fc_subfield_instance_sp = $fc_subfield_instance_pt = $fc_subfield_instance;
    
    // prepare properties for saving
    $fc_subfield_instance_sp['bundle'] = $instance_sp['field_name'];
    $fc_subfield_instance_pt['bundle'] = $instance_pt['field_name'];
    $fc_subfield_instance_sp['required'] = $fc_subfield_instance_pt['required'] = FALSE;
    
    field_create_instance($fc_subfield_instance_sp);
    field_create_instance($fc_subfield_instance_pt);
    
  }

}

artatum’s picture

Hello
There is useful things here, but I dont find how actually creating a fc in a module : where do we declare the fc, I mean what is the right hook_???_info for fc ? Is there any beginner sample module code somewhere ?

kenorb’s picture

logicp’s picture

artatum: You would declare appropriate functions in the module and then have them called for specific circumstances. These circumstances could include the submission of a form, a call to Services API, a cron job retrieving data from a database, etc. The answer all depends on what you are trying to create and under what conditions.

Napche’s picture

A little sidenote :

I was struggling with this to prepopulate an entityForm using the prepareEntity function.

In the end I found out that setHostEntity actually saves the host entity, so it might be wise to save that function till the end.

$field_collection_item = FieldCollectionItem::create(['field_name' => 'field_my_collection']);
$field_collection_item->set('field_my_field', 'some_value');
$field_collection_item->setHostEntity($entity);
$field_collection_item->save();