Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
While using field_attach_update() to update certain fields in a node object I noticed that file fields are always cleared if not included in the passed entity object. This doesn't seem to be a problem for any other field type.
To reproduce:
- Install Drupal with the standard modules.
- Add the existing field_image field to the "Basic Page" content type.
- Add a standard text field called field_text
- Add another standard text field called field_text2
- Create a basic page, uploading an image to field_image and placing text in all the other fields
Try to update the text field only with field_attach_update(). In the code below I'm assuming the content created above is nid #1.
$node = node_load(1);
$update_node = new stdClass();
$update_node->nid = $node->nid;
$update_node->vid = $node->vid;
$update_node->type = $node->type;
$update_node->field_text['und'][0]['value'] = 'Some new text!';
field_attach_update('node', $update_node);
Result:
field_text field is successfully updated but the image is deleted and the field is set to null. field_text2 is left alone as expected.
Desired result:
Only field_text is updated. All other fields are left alone.
Comment | File | Size | Author |
---|---|---|---|
#19 | drupal7-1483736-11-testonly.patch | 1.01 KB | stefan.r |
#19 | drupal7-1483736-11-withtest.patch | 1.77 KB | stefan.r |
#11 | drupal7-1483736-11.patch | 774 bytes | stefan.r |
Comments
Comment #1
hawkeye.twolfI can confirm that the steps posted in this issue do lead to the reported data loss (field_image). However, I am marking this issue "closed (works as designed)" because the posted code does not use function field_attach_update as it was intended. See this code that uses field_attach_update as intended and works correctly:
Comment #2
emilymoi CreditAttribution: emilymoi commentedI was about to post the same bug report and I can across this one describing the problem I am running into.
My understanding is that drupal should allow for saving of partial nodes. There is code that handles partial nodes in node_save and also in field_attach_update. An empty array != a missing field. This is handled all over the place.
Now who is at fault here? file_field_update() gets passed an empty array of $items so it really has no idea whether the entity had the field set or not. If we look upstream we find that in _field_invoke() does a check to see if the field is empty and if it is uses an empty array placeholder. That empty array is then passed in to the field hooks. Now, what I don't understand is why does _field_invoke() pass an empty $items array to the field_hooks on update? Why does it even need to call the hook_field_update() when the field does not exist? Is there something that relies on this? Seems like a bug to me.
Here is what I am currently using as a work-around for this problem. No idea what sort of side effects this will cause but IMO it makes no sense to update when the field does not exist.
Comment #3
emilymoi CreditAttribution: emilymoi commentedComment #4
marcingy CreditAttribution: marcingy commentedThis works as designed
Comment #5
j0rd CreditAttribution: j0rd commentedI just got bitten by a couple weeks of this on a user content generated site where I'm using back referencing entity references. Started to lose media from those who ended up touching my field_attach_update() code on a slimmed down object.
With this all said, has anyone found a proper way to update a single field for a node with out doing superfluous queries for fields which never get touched.
In my case, I allow my users to associate their content with other peoples content. Each of these content items have 50 or so fields. They can easily create as many associations as they want via a custom widget I've created. Upon save, I update a single field in the associated nodes, which links back to the node which the original author was creating these associations from. It's not uncommon for a user to associate themselves with 20 or so other nodes, so saving each field for each node creates hundreds of updates / insert queries which I'd like to avoid as it won't scale.
Any assistance would be appreciated.
Comment #6
hedley CreditAttribution: hedley commentedI just got stung by this issue. There doesn't seem to be any information in the field_attach_update() documentation on how to use it properly, it might be a good idea to add something in?
Comment #7
stefan.r CreditAttribution: stefan.r commentedI got stung by this as well while implementing https://www.urbaninsight.com/2011/10/24/saving-nodes-fields-without-savi...
Regardless of how this was "designed", it seems a lot of people believe you can use field_attach_update() to update a single field using a partial node.
Comment #8
stefan.r CreditAttribution: stefan.r commentedOther pages that suggest using field_attach_update() is safe:
https://drupal.stackexchange.com/questions/60713/update-a-single-field-w...
https://drupal.stackexchange.com/questions/64833/fast-saving-single-fiel...
As well as the comments in the documentation for field_attach_update():
https://api.drupal.org/api/drupal/modules!field!field.attach.inc/functio...
So we can only use field_sql_storage_field_storage_write() instead?
Comment #9
mikeytown2 CreditAttribution: mikeytown2 commentedIf this function shouldn't be called directly like this it should have been named _field_attach_update(). Since we can't rename it we need to fix this. While doing a bulk drush operation it nuked less than 1% of the files but still over 1,000 on our setup.
Other modules use it directly as well #2353491: File Usage count for revisions out of sync/incorrect not realizing how this might cause data loss.
Even the documentation for this acknowledges that this might be called outside of the normal way of doing things https://api.drupal.org/api/drupal/modules!field!field.attach.inc/functio...
Related:
#1141912: Changing node language by moving down language list deletes files in file field
#1399846: Make unused file 'cleanup' configurable
#1968442: RFC: How to prevent files from being deleted by file/media fields when unused
Comment #10
stefan.r CreditAttribution: stefan.r commentedData loss so upgrading priority
Comment #11
stefan.r CreditAttribution: stefan.r commentedJust to explain what happens here, field_attach_update() does
_field_invoke('update', $entity_type, $entity);
, which iterates through all the defined field instances of the bundle, doing the following:So this calls
file_field_update()
in order to "check for files that have been removed from the object", regardless of whether $entity->{$field_name} is even defined. So the value of $items will be an empty array regardless of whether the field is defined but empty/"file-less" or whether the field property is not even defined.I guess we could either change
file_field_update()
(as in this patch), orfield_invoke()
directly?Comment #12
stefan.r CreditAttribution: stefan.r commentedComment #13
mikeytown2 CreditAttribution: mikeytown2 commentedJust tested the patch in #11 and this fixes the issue.
Comment #14
ShaneOnABike CreditAttribution: ShaneOnABike commentedI can also confirm that this works as expected. I agree that this is a fairly serious problem since it means that anyone not using node_save and directly saving fields unrelated to a file results in lost data.
Comment #15
jtsnow CreditAttribution: jtsnow commentedI work on a large Drupal project that was recently affected by this. I can confirm that the patch fixes the problem.
Comment #16
Fabianx CreditAttribution: Fabianx for Drupal Association commentedThis will need a test and can anyone confirm that this is no longer an issue in 8.0.x?
Comment #17
Fabianx CreditAttribution: Fabianx commentedPer #1126000: [meta] hook_field_*() called on fields not set in the $entity object (partial updates) this is not a problem in 8.0.x and a problem in D7, so this only needs tests.
Comment #18
Fabianx CreditAttribution: Fabianx commentedComment #19
stefan.r CreditAttribution: stefan.r commentedComment #21
Fabianx CreditAttribution: Fabianx commentedBack to RTBC, thanks for the tests!
Comment #22
David_Rothstein CreditAttribution: David_Rothstein commentedThis is OK as a sane fallback behavior, but I really don't see how passing in anything other than a complete entity is supposed to be supported by the field API, so I'm lowering the priority...
Committed to 7.x - thanks!
Comment #25
kikecastillo CreditAttribution: kikecastillo commentedI would like to share with you some issues you can have if you don't use db_transaction using field_attach_update function:
field_attach_update function is normally used within node_save function and then it is done "transactional". If this function is used without using db_transaction function before it is possible to lose content because field_attach_update calls field_sql_storage_field_storage_write function that will delete and insert fields in the database. If this function is not transactional then it could happen a wrong database state during the execution and lose content.
Example:
$transaction = db_transaction();
try {
$node = node_load(1); // where 1 is the nid you want to change
$update_node = new stdClass();
$update_node->nid = $node->nid;
$update_node->vid = $node->vid;
$update_node->type = $node->type;
$update_node->field_FIELD_NAME['und'][0]['value'] = VALUE_YOU_WANT_TO_CHANGE;
field_attach_update('node', $update_node);
} catch (Exception $e) {
$transaction->rollback();
throw $e;
}