I had the challenge of creating a new node containing multivalue field collections within multivalue field collections, etc.
This posed no problem as such, except for perhaps performance. Whenever a field collection is saved, the host entity is saved also. In my case it caused a save of the node 26 times, which is perhaps a bit overkill for a new node.
The FieldCollectionItem entity has an internal parameter ($skip_host_save) on the save() method. However, this is not availabe via entity_save() or via the entity metadata wrapper, the latter which I use heavily.
The class below will allow you to call an entity object's save() method with arguments via an entity metadata wrapper object.
/**
/**
* Class EntityDrupalWrapperSaveArguments
*
* Hax0r class for saving field collection without saving the host entity.
*
* When saving a new field collection, the host entity will be saved as well.
* This can result in several of unnecessary (1) saves of the host entity,
* especially if creating a new node with many field collection fields.
*
* The FieldCollectionItem::save() supports an internal option for only saving
* the field collection entity, but there's no way to send this option via
* entity_save() or EntityDrupalWrapper::save(). Only through Entity::save() is
* this possible. However, if we use Entity::save() directly, we loose all the
* benefits of the metadata wrapper.
*
* Instead we implement a "pseudo" class, which has access to the entity
* metadata wrapper object's protected variables.
*
* (1) They SEEM unnecessary.
*/
class EntityDrupalWrapperSaveArguments extends EntityDrupalWrapper {
/**
* Re-implementation of EntityDrupalWrapper->save().
*
* When saving a new entity, the wrapper object's id must be updated.
* Since this is a protected variable, we implement this method in a class
* "pretending" to be an EntityDrupalWrapper class. Thereby we gain access to
* protected variables in other objects of the same type.
*
* @see EntityDrupalWrapper::save().
*/
static public function saveArguments() {
$args = func_get_args();
$wrapper = array_shift($args);
if ($wrapper->data) {
if (!entity_type_supports($wrapper->type, 'save')) {
throw new EntityMetadataWrapperException("There is no information about how to save entities of type " . check_plain($wrapper->type) . '.');
}
self::entity_save_arguments($wrapper->type, $wrapper->data, $args);
// On insert, update the identifier afterwards.
if (!$wrapper->id) {
list($wrapper->id, , ) = entity_extract_ids($wrapper->type, $wrapper->data);
}
}
// If the entity hasn't been loaded yet, don't bother saving it.
return $wrapper;
}
/**
* Re-implementation of entity_save().
*
* In order to send arguments to the entity's save() method, we need to
* re-implement the logic from entity_save().
*
* This function takes an extra arguments ($args) compared to entity_save().
* $args contains an array of the arguments passed to $entity->save().
*
* Note: ^ There's a difference between entity_save() and $entity->save().
*
* @see entity_save().
* @see Entity::save().
*/
static public function entity_save_arguments($entity_type, $entity, $args) {
$info = entity_get_info($entity_type);
if (method_exists($entity, 'save')) {
return call_user_func_array(array($entity, 'save'), $args);
}
elseif (isset($info['save callback'])) {
$info['save callback']($entity);
}
elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
return entity_get_controller($entity_type)->save($entity);
}
else {
return FALSE;
}
}
}
// Create an Entity and populate it
$entity = entity_create('node', array('type' => 'article'));
$entity->uid = 1;
$entity->title = 'test';
$wrapper = entity_metadata_wrapper('node', $entity);
$fc_entity = entity_create('field_collection_item', array('field_name' => 'field_my_field_collection_field'));
$fc_entity->setHostEntity('node', $entity);
$fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc_entity);
$fc_wrapper->field_my_field_inside_the_field_collection->set('some value');
// Old style save.
// $fc_wrapper->save();
// New style save.
// Equivalent to $fc_entity->save(TRUE), but retains the functionality of the metadata wrapper.
EntityDrupalWrapperSaveArguments::saveArguments($fc_wrapper, TRUE);
Disclaimer: I'm not responsible if you hurt yourself with this code. And be aware, that using this code will also bypass some presave/update/insert handlers. Which could hurt you. Big time. And I'm not responsible.