When an entity (say, a node) has a field_collection, all you get when you load the node is the id of the field_collection entity.

$node->field_coll[$lang] = array(
  array('value' => 12),
  array('value' => 15),
);

And you need to separately load the field_collection 12 to access the values.

It would be handy to load the full entity within the loaded field value in field_collection_field_load() - a bit like file field does :

$node->field_coll[$lang] = array(
  array(
    'value' => 12,
    'field_combo_1' => array(... the values ...),
    'field_combo_2' => array(... the values ...)
  ),
  array(
    'value' => 15,
    'field_combo_1' => array(... the values ...),
    'field_combo_2' => array(... the values ...),
  ),
);

Gotcha with field cache - this means clearing the field cache of the host node when the field_collection is updated separately.

Then for extra handy programmatic manipulation, handle this format on save :

$node = node_load();
// Update 1st collection :
$node->field_coll['und'][0]['field_combo_1']['und'] = array(new values);
// Add a new collection on the fly :
$new_coll = new stdClass();
$new_coll->field_combo_1 = array(... the values ...);
$new_coll->field_combo_2 = array(... the values ...);
$node->field_coll['und'][] = (array) $new_coll;
node_save($node);

This means that the widgets have to produce that format on form submission, though.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

yched’s picture

Edit, turns out that the save is already possible, and used by the widgets :

$new_coll = entity_create('field_collection_item', array('field_name' => 'field_combo_1'));
$new_coll->is_new = TRUE;
$new_coll->field_combo_1 = array(... the values ...);
$new_coll->field_combo_2 = array(... the values ...);
$node->field_coll['und'][] = array('entity' => $new_coll);
node_save($node);

So it would be just a question of populating the 'entity' entry on field load ?

yched’s picture

A collection item saved individually triggers a save of its host entity, which takes care of clearing the cache. So it's just a matter of populating the 'entity' key on field load.
Additionally, performance++ for widgets and formatters (esp. on node listing pages) since this uses an entity_load_multiple() that spans over all the host entities listed on the page, rather than by host and by collection item.

I don't think we should run into infinite load loops, since a given collection item has one single host, and can therefore not be included inside itself, right ? (unlike a taxo term, or a noderef).

Patch attached.

Makes the following code possible :

$node = node_load(9);
// Access values.
foreach ($node->field_combo['und'] as $delta => $item) {
  dsm($item['entity']->field_combo_number['und'][0]['value']);
  dsm($item['entity']->field_combo_text['und'][0]['value']);
}
// Update existing.
$item = &$node->field_combo['und'][0]['entity'];
$item->field_combo_number['und'][0] = array('value' => 3);
// Add new.
$new = entity_create('field_collection_item', array('field_name' => 'field_combo'));
$new->field_combo_number['und'][] = array('value' => 1);
$new->field_combo_text['und'][] = array('value' => 'A');
$node->field_combo['und'][] = array('entity' => $new);
// Save collection items with the host node.
node_save($node);
yched’s picture

Status: Active » Needs review
FileSize
5.46 KB

Forget patch #2, forgot to commit last local changes and revert #1227244: Bug in formatter settings in Views before rolling.

rjbrown99’s picture

+1 for me. I'll explain my use case.

I am adding a field collection to users, and I successfully added the collection (called field_role) using the embedded widget and defined two content taxonomy fields. This all works, and the fields show up on the user edit page. What I am noticing however is that when I programmatically do a user_load($uid); the returned object has a field definition that looks like your example from the top:

    [field_role] => Array
        (
            [und] => Array
                (
                    [0] => Array
                        (
                            [value] => 1
                        )
                    [1] => Array
                        (
                            [value] => 2
                        )
                )
        )

That's not particularly helpful to me, as I need to both create users programmatically (while populating this collection) as well as update existing users when values change. It seems like this patch would help with that.

rjbrown99’s picture

Nice, the patch from #3 works great for my use case. In addition to what was showing in my post #4 above, under [value] I now see a populated [entity] object. I hope this patch makes it in as I'm going to start implementing stuff based on it :)

valderama’s picture

When I use this patch, field_collection items can not be added anymore to the host entity by using the "add" link ( eg field-collection/field-bla/add/field_collection_item/853)

Can you imagine any reason for that? Or can you confirm this - or does it work for you?

rjbrown99’s picture

I have not tried to add new fields, I am using this with fields that were already created prior to applying the patch.

fago’s picture

I'm not sure we should always auto-load all the entities. Wouldn't it be more efficient and better for performance to only load them if needed? So e.g. a host entity using the hidden widget would not have to load + save all field-collections during node edit.

For easy access the entities one can already use the metadata wrappers, like this:

    $wrapper = entity_metadata_wrapper('node', $node);
    echo $wrapper->$collection_name->field_tags[0]->name->value();
    $wrapper->$collection_name->field_text->set('foo');

For adding a new collection simply do:

    $entity = entity_create('field_collection_item', array('field_name' => $collection_name));
    $entity->setHostEntity('node', $node);
yched’s picture

Dunno, might be argued, I guess.

I wasn't aware that the Entity API accessors allowed some transparent loading. The problem with loading them on demand (even if autoloaded wrapped in the entity API accessor) is that you load the deltas one by one (and entity by entity for listing pages), without benefiting from a single multiload.

fago’s picture

Yep. You could do

    $wrapper->$collection_name->value()

too, which gives you all field collection items and makes use of multiple loading.

valderama’s picture

cool - that hints helped :) thx, fago..

tim.plunkett’s picture

patchshorts’s picture

any news on this? I've ran all the patches on this and #1259916-3: Load reference fields when building content but I dont get my values loaded into the $node or $profile2 objects.

Status: Needs review » Needs work

The last submitted patch, entity_on_load-1227800-3.patch, failed testing.

devtherock’s picture

Hi,

Does anybody know how we can add multiple fields (with collection of fields) in my custom module while user adding a new node. I want to show pre fill fields to user.

Thanks.

edmund.kwok’s picture

Any updates on this? I'm having difficulty saving values to fields in a field collection with multiple values.

field_addresses is the name of the field collection.

$entity = entity_metadata_wrapper('person', 1, array('bundle' => 'person'));

// Both doesn't work
$entity->field_addresses[0]->field_first_line = 'No, Street Name';
$entity->field_addresses[0]->field_first_line->set('No, Street Name');
entity_save('person', $entity);

Any ideas?

kevinquillen’s picture

How do you load a Field Collection when an entity is loaded? Is that in dev? I am importing data in and need to manipulate data programmatically to create/update entities.

tim.plunkett’s picture

See the documentation: http://drupal.org/node/1353926

kevinquillen’s picture

How can you use EFQ to query and find data that already exists in a field collection, instead of overwrite it? I tried:


$result = $query->entityCondition('entity_type', 'field_collection_item')
			->propertyCondition('entity_id', $mycustomentity->id)
			->fieldCondition('field_basis', 'value', $date['basis'], '=')
			->fieldCondition('field_period', 'value', $date['startdate'], '=')
			->fieldCondition('field_period', 'value2', $date['enddate'], '=')
			->execute();

But I got SQL errors pertaining to field joins. I see the comment (http://drupal.org/node/1353926#comment-5342968) that shows how to load each one, but I want to load up a particular item matching certain values, unless there is another way to perform a field collection item update programmatically (without knowing its delta/id) other than this?

Fago's suggestion in #10 gives me all the data, but would it be faster to pick it out with the criteria I am looking for instead of looping and looking?

geek-merlin’s picture